Browse Source
feat(files_sharing): show Account menu on public pages
feat(files_sharing): show Account menu on public pages
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>pull/52537/head
24 changed files with 492 additions and 245 deletions
-
5apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
-
10apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php
-
57apps/files_sharing/src/public-file-request.ts
-
86apps/files_sharing/src/public-nickname-handler.ts
-
138apps/files_sharing/src/views/PublicAuthPrompt.vue
-
2apps/files_sharing/tests/Controller/ShareControllerTest.php
-
9core/src/components/AccountMenu/AccountMenuEntry.vue
-
8core/src/components/PublicPageMenu/PublicPageMenuEntry.vue
-
15core/src/public-page-user-menu.ts
-
2core/src/views/AccountMenu.vue
-
135core/src/views/PublicPageUserMenu.vue
-
1core/templates/layout.public.php
-
10cypress/e2e/files_sharing/public-share/PublicShareUtils.ts
-
2cypress/e2e/files_sharing/public-share/copy-move-files.cy.ts
-
8cypress/e2e/files_sharing/public-share/default-view.cy.ts
-
6cypress/e2e/files_sharing/public-share/download.cy.ts
-
193cypress/e2e/files_sharing/public-share/header-avatar.cy.ts
-
2cypress/e2e/files_sharing/public-share/header-menu.cy.ts
-
2cypress/e2e/files_sharing/public-share/rename-files.cy.ts
-
24cypress/e2e/files_sharing/public-share/required-before-create.cy.ts
-
1lib/public/AppFramework/Http/Template/PublicTemplateResponse.php
-
14package-lock.json
-
4package.json
-
3webpack.modules.js
@ -1,57 +0,0 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
import { defineAsyncComponent } from 'vue' |
|||
import { getBuilder } from '@nextcloud/browser-storage' |
|||
import { getGuestNickname, setGuestNickname } from '@nextcloud/auth' |
|||
import { getUploader } from '@nextcloud/upload' |
|||
import { spawnDialog } from '@nextcloud/dialogs' |
|||
|
|||
import logger from './services/logger' |
|||
|
|||
const storage = getBuilder('files_sharing').build() |
|||
|
|||
/** |
|||
* Setup file-request nickname header for the uploader |
|||
* @param nickname The nickname |
|||
*/ |
|||
function registerFileRequestHeader(nickname: string) { |
|||
const uploader = getUploader() |
|||
uploader.setCustomHeader('X-NC-Nickname', encodeURIComponent(nickname)) |
|||
logger.debug('Nickname header registered for uploader', { headers: uploader.customHeaders }) |
|||
} |
|||
|
|||
/** |
|||
* Callback when a nickname was chosen |
|||
* @param nickname The chosen nickname |
|||
*/ |
|||
function onSetNickname(nickname: string): void { |
|||
// Set the nickname
|
|||
setGuestNickname(nickname) |
|||
// Set the dialog as shown
|
|||
storage.setItem('public-auth-prompt-shown', 'true') |
|||
// Register header for uploader
|
|||
registerFileRequestHeader(nickname) |
|||
} |
|||
|
|||
window.addEventListener('DOMContentLoaded', () => { |
|||
const nickname = getGuestNickname() ?? '' |
|||
const dialogShown = storage.getItem('public-auth-prompt-shown') !== null |
|||
|
|||
// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
|
|||
// We still show the prompt if the user has a nickname to double check
|
|||
if (!nickname || !dialogShown) { |
|||
spawnDialog( |
|||
defineAsyncComponent(() => import('./views/PublicAuthPrompt.vue')), |
|||
{ |
|||
nickname, |
|||
}, |
|||
onSetNickname as (...rest: unknown[]) => void, |
|||
) |
|||
} else { |
|||
logger.debug('Public auth prompt already shown.', { nickname }) |
|||
registerFileRequestHeader(nickname) |
|||
} |
|||
}) |
|||
@ -0,0 +1,86 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
import { getBuilder } from '@nextcloud/browser-storage' |
|||
import { getGuestNickname, type NextcloudUser } from '@nextcloud/auth' |
|||
import { getUploader } from '@nextcloud/upload' |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import { showGuestUserPrompt } from '@nextcloud/dialogs' |
|||
import { t } from '@nextcloud/l10n' |
|||
|
|||
import logger from './services/logger' |
|||
import { subscribe } from '@nextcloud/event-bus' |
|||
|
|||
const storage = getBuilder('files_sharing').build() |
|||
|
|||
// Setup file-request nickname header for the uploader
|
|||
const registerFileRequestHeader = (nickname: string) => { |
|||
const uploader = getUploader() |
|||
uploader.setCustomHeader('X-NC-Nickname', encodeURIComponent(nickname)) |
|||
logger.debug('Nickname header registered for uploader', { headers: uploader.customHeaders }) |
|||
} |
|||
|
|||
// Callback when a nickname was chosen
|
|||
const onUserInfoChanged = (guest: NextcloudUser) => { |
|||
logger.debug('User info changed', { guest }) |
|||
registerFileRequestHeader(guest.displayName ?? '') |
|||
} |
|||
|
|||
// Monitor nickname changes
|
|||
subscribe('user:info:changed', onUserInfoChanged) |
|||
|
|||
window.addEventListener('DOMContentLoaded', () => { |
|||
const nickname = getGuestNickname() ?? '' |
|||
const dialogShown = storage.getItem('public-auth-prompt-shown') !== null |
|||
|
|||
// Check if a nickname is mandatory
|
|||
const isFileRequest = loadState('files_sharing', 'isFileRequest', false) |
|||
|
|||
const owner = loadState('files_sharing', 'owner', '') |
|||
const ownerDisplayName = loadState('files_sharing', 'ownerDisplayName', '') |
|||
const label = loadState('files_sharing', 'label', '') |
|||
const filename = loadState('files_sharing', 'filename', '') |
|||
|
|||
// If the owner provided a custom label, use it instead of the filename
|
|||
const folder = label || filename |
|||
|
|||
const options = { |
|||
nickname, |
|||
notice: t('files_sharing', 'To upload files to {folder}, you need to provide your name first.', { folder }), |
|||
subtitle: undefined as string | undefined, |
|||
title: t('files_sharing', 'Upload files to {folder}', { folder }), |
|||
} |
|||
|
|||
// If the guest already has a nickname, we just make them double check
|
|||
if (nickname) { |
|||
options.notice = t('files_sharing', 'Please confirm your name to upload files to {folder}', { folder }) |
|||
} |
|||
|
|||
// If the account owner set their name as public,
|
|||
// we show it in the subtitle
|
|||
if (owner) { |
|||
options.subtitle = t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) |
|||
} |
|||
|
|||
// If this is a file request, then we need a nickname
|
|||
if (isFileRequest) { |
|||
// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
|
|||
// We still show the prompt if the user has a nickname to double check
|
|||
if (!nickname || !dialogShown) { |
|||
logger.debug('Showing public auth prompt.', { nickname }) |
|||
showGuestUserPrompt(options) |
|||
} |
|||
return |
|||
} |
|||
|
|||
if (!dialogShown && !nickname) { |
|||
logger.debug('Public auth prompt not shown yet but nickname is not mandatory.', { nickname }) |
|||
return |
|||
} |
|||
|
|||
// Else, we just register the nickname header if any.
|
|||
logger.debug('Public auth prompt already shown.', { nickname }) |
|||
registerFileRequestHeader(nickname) |
|||
}) |
|||
@ -1,138 +0,0 @@ |
|||
<!-- |
|||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
- SPDX-License-Identifier: AGPL-3.0-or-later |
|||
--> |
|||
|
|||
<template> |
|||
<NcDialog :buttons="dialogButtons" |
|||
class="public-auth-prompt" |
|||
data-cy-public-auth-prompt-dialog |
|||
is-form |
|||
:can-close="false" |
|||
:name="dialogName" |
|||
@submit="$emit('close', name)"> |
|||
<p v-if="owner" class="public-auth-prompt__subtitle"> |
|||
{{ t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) }} |
|||
</p> |
|||
|
|||
<!-- Header --> |
|||
<NcNoteCard class="public-auth-prompt__header" |
|||
:text="t('files_sharing', 'To upload files, you need to provide your name first.')" |
|||
type="info" /> |
|||
|
|||
<!-- Form --> |
|||
<NcTextField ref="input" |
|||
class="public-auth-prompt__input" |
|||
data-cy-public-auth-prompt-dialog-name |
|||
:label="t('files_sharing', 'Name')" |
|||
:placeholder="t('files_sharing', 'Enter your name')" |
|||
minlength="2" |
|||
name="name" |
|||
required |
|||
:value.sync="name" /> |
|||
</NcDialog> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue' |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import { t } from '@nextcloud/l10n' |
|||
|
|||
import NcDialog from '@nextcloud/vue/components/NcDialog' |
|||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' |
|||
import NcTextField from '@nextcloud/vue/components/NcTextField' |
|||
|
|||
import { getGuestNameValidity } from '../services/GuestNameValidity' |
|||
|
|||
export default defineComponent({ |
|||
name: 'PublicAuthPrompt', |
|||
|
|||
components: { |
|||
NcDialog, |
|||
NcNoteCard, |
|||
NcTextField, |
|||
}, |
|||
|
|||
props: { |
|||
/** |
|||
* Preselected nickname |
|||
* @default '' No name preselected by default |
|||
*/ |
|||
nickname: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
}, |
|||
|
|||
setup() { |
|||
return { |
|||
t, |
|||
|
|||
owner: loadState('files_sharing', 'owner', ''), |
|||
ownerDisplayName: loadState('files_sharing', 'ownerDisplayName', ''), |
|||
label: loadState('files_sharing', 'label', ''), |
|||
note: loadState('files_sharing', 'note', ''), |
|||
filename: loadState('files_sharing', 'filename', ''), |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
name: '', |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
dialogName() { |
|||
return this.t('files_sharing', 'Upload files to {folder}', { folder: this.label || this.filename }) |
|||
}, |
|||
dialogButtons() { |
|||
return [{ |
|||
label: t('files_sharing', 'Submit name'), |
|||
type: 'primary', |
|||
nativeType: 'submit', |
|||
}] |
|||
}, |
|||
}, |
|||
|
|||
watch: { |
|||
/** Reset name to pre-selected nickname (e.g. Talk / Collabora ) */ |
|||
nickname: { |
|||
handler() { |
|||
this.name = this.nickname |
|||
}, |
|||
immediate: true, |
|||
}, |
|||
|
|||
name() { |
|||
// Check validity of the new name |
|||
const newName = this.name.trim?.() || '' |
|||
const input = (this.$refs.input as Vue|undefined)?.$el.querySelector('input') |
|||
if (!input) { |
|||
return |
|||
} |
|||
|
|||
const validity = getGuestNameValidity(newName) |
|||
input.setCustomValidity(validity) |
|||
input.reportValidity() |
|||
}, |
|||
}, |
|||
}) |
|||
</script> |
|||
<style scoped lang="scss"> |
|||
.public-auth-prompt { |
|||
&__subtitle { |
|||
// Smaller than dialog title |
|||
font-size: 1.25em; |
|||
margin-block: 0 calc(3 * var(--default-grid-baseline)); |
|||
} |
|||
|
|||
&__header { |
|||
margin-block: 0 calc(3 * var(--default-grid-baseline)); |
|||
} |
|||
|
|||
&__input { |
|||
margin-block: calc(4 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline)); |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,15 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
|
|||
import { getCSPNonce } from '@nextcloud/auth' |
|||
import Vue from 'vue' |
|||
|
|||
import PublicPageUserMenu from './views/PublicPageUserMenu.vue' |
|||
|
|||
__webpack_nonce__ = getCSPNonce() |
|||
|
|||
const View = Vue.extend(PublicPageUserMenu) |
|||
const instance = new View() |
|||
instance.$mount('#public-page-user-menu') |
|||
@ -0,0 +1,135 @@ |
|||
<!-- |
|||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
- SPDX-License-Identifier: AGPL-3.0-or-later |
|||
--> |
|||
<template> |
|||
<NcHeaderMenu id="public-page-user-menu" |
|||
class="public-page-user-menu" |
|||
is-nav |
|||
:aria-label="t('core', 'User menu')" |
|||
:description="avatarDescription"> |
|||
<template #trigger> |
|||
<NcAvatar class="public-page-user-menu__avatar" |
|||
disable-menu |
|||
disable-tooltip |
|||
is-guest |
|||
:user="displayName || '?'" /> |
|||
</template> |
|||
<ul class="public-page-user-menu__list"> |
|||
<!-- Privacy notice --> |
|||
<NcNoteCard class="public-page-user-menu__list-note" |
|||
:text="privacyNotice" |
|||
type="info" /> |
|||
|
|||
<!-- Nickname dialog --> |
|||
<AccountMenuEntry id="set-nickname" |
|||
:name="!displayName ? t('core', 'Set public name') : t('core', 'Change public name')" |
|||
href="#" |
|||
@click.prevent.stop="setNickname"> |
|||
<template #icon> |
|||
<IconAccount /> |
|||
</template> |
|||
</AccountMenuEntry> |
|||
</ul> |
|||
</NcHeaderMenu> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import type { NextcloudUser } from '@nextcloud/auth' |
|||
|
|||
import '@nextcloud/dialogs/style.css' |
|||
import { defineComponent } from 'vue' |
|||
import { getGuestUser } from '@nextcloud/auth' |
|||
import { showGuestUserPrompt } from '@nextcloud/dialogs' |
|||
import { subscribe } from '@nextcloud/event-bus' |
|||
import { t } from '@nextcloud/l10n' |
|||
|
|||
import NcAvatar from '@nextcloud/vue/components/NcAvatar' |
|||
import NcHeaderMenu from '@nextcloud/vue/components/NcHeaderMenu' |
|||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' |
|||
import IconAccount from 'vue-material-design-icons/Account.vue' |
|||
|
|||
import AccountMenuEntry from '../components/AccountMenu/AccountMenuEntry.vue' |
|||
|
|||
export default defineComponent({ |
|||
name: 'PublicPageUserMenu', |
|||
components: { |
|||
AccountMenuEntry, |
|||
IconAccount, |
|||
NcAvatar, |
|||
NcHeaderMenu, |
|||
NcNoteCard, |
|||
}, |
|||
|
|||
setup() { |
|||
return { |
|||
t, |
|||
} |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
displayName: getGuestUser().displayName, |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
avatarDescription(): string { |
|||
return t('core', 'User menu') |
|||
}, |
|||
|
|||
privacyNotice(): string { |
|||
return this.displayName |
|||
? t('core', 'You will be identified as {user} by the account owner.', { user: this.displayName }) |
|||
: t('core', 'You are currently not identified.') |
|||
}, |
|||
}, |
|||
|
|||
mounted() { |
|||
subscribe('user:info:changed', (user: NextcloudUser) => { |
|||
this.displayName = user.displayName || '' |
|||
}) |
|||
}, |
|||
|
|||
methods: { |
|||
setNickname() { |
|||
showGuestUserPrompt({ |
|||
nickname: this.displayName, |
|||
cancellable: true, |
|||
}) |
|||
}, |
|||
}, |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.public-page-user-menu { |
|||
box-sizing: border-box; |
|||
|
|||
// Ensure we do not waste space, as the header menu sets a default width of 350px |
|||
:deep(.header-menu__content) { |
|||
width: fit-content !important; |
|||
} |
|||
|
|||
&__list-note { |
|||
padding-block: 5px !important; |
|||
padding-inline: 5px !important; |
|||
max-width: 300px; |
|||
margin: 5px !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
&__list { |
|||
display: inline-flex; |
|||
flex-direction: column; |
|||
padding-block: var(--default-grid-baseline) 0; |
|||
padding-inline: 0 var(--default-grid-baseline); |
|||
|
|||
> :deep(li) { |
|||
box-sizing: border-box; |
|||
// basically "fit-content" |
|||
flex: 0 1; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,193 @@ |
|||
/*! |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
import type { ShareContext } from './PublicShareUtils.ts' |
|||
import { createLinkShare, setupData } from './PublicShareUtils.ts' |
|||
|
|||
/** |
|||
* This tests ensures that on public shares the header avatar menu correctly works |
|||
*/ |
|||
describe('files_sharing: Public share - header avatar menu', { testIsolation: true }, () => { |
|||
let context: ShareContext |
|||
let firstPublicShareUrl = '' |
|||
let secondPublicShareUrl = '' |
|||
|
|||
before(() => { |
|||
cy.createRandomUser() |
|||
.then((user) => { |
|||
context = { |
|||
user, |
|||
url: undefined, |
|||
} |
|||
setupData(context.user, 'public1') |
|||
setupData(context.user, 'public2') |
|||
createLinkShare(context, 'public1').then((shareUrl) => { |
|||
firstPublicShareUrl = shareUrl |
|||
cy.log(`Created first share with URL: ${shareUrl}`) |
|||
}) |
|||
createLinkShare(context, 'public2').then((shareUrl) => { |
|||
secondPublicShareUrl = shareUrl |
|||
cy.log(`Created second share with URL: ${shareUrl}`) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
beforeEach(() => { |
|||
cy.logout() |
|||
cy.visit(firstPublicShareUrl) |
|||
}) |
|||
|
|||
it('See the undefined avatar menu', () => { |
|||
cy.get('header') |
|||
.findByRole('navigation', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.findByRole('button', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.click() |
|||
cy.get('#header-menu-public-page-user-menu') |
|||
.as('headerMenu') |
|||
|
|||
// Note that current guest user is not identified
|
|||
cy.get('@headerMenu') |
|||
.should('be.visible') |
|||
.findByRole('note') |
|||
.should('be.visible') |
|||
.should('contain', 'not identified') |
|||
|
|||
// Button to set guest name
|
|||
cy.get('@headerMenu') |
|||
.findByRole('link', { name: /Set public name/i }) |
|||
.should('be.visible') |
|||
}) |
|||
|
|||
it('Can set public name', () => { |
|||
cy.get('header') |
|||
.findByRole('navigation', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.findByRole('button', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.as('userMenuButton') |
|||
|
|||
// Open the user menu
|
|||
cy.get('@userMenuButton').click() |
|||
cy.get('#header-menu-public-page-user-menu') |
|||
.as('headerMenu') |
|||
|
|||
cy.get('@headerMenu') |
|||
.findByRole('link', { name: /Set public name/i }) |
|||
.should('be.visible') |
|||
.click() |
|||
|
|||
// Check the dialog is visible
|
|||
cy.findByRole('dialog', { name: /Guest identification/i }) |
|||
.should('be.visible') |
|||
.as('guestIdentificationDialog') |
|||
|
|||
// Check the note is visible
|
|||
cy.get('@guestIdentificationDialog') |
|||
.findByRole('note') |
|||
.should('contain', 'not identified') |
|||
|
|||
// Check the input is visible
|
|||
cy.get('@guestIdentificationDialog') |
|||
.findByRole('textbox', { name: /Name/i }) |
|||
.should('be.visible') |
|||
.type('{selectAll}John Doe{enter}') |
|||
|
|||
// Check that the dialog is closed
|
|||
cy.get('@guestIdentificationDialog') |
|||
.should('not.exist') |
|||
|
|||
// Check that the avatar changed
|
|||
cy.get('@userMenuButton') |
|||
.find('img') |
|||
.invoke('attr', 'src') |
|||
.should('include', 'avatar/guest/John%20Doe') |
|||
}) |
|||
|
|||
it('Guest name us persistent and can be changed', () => { |
|||
cy.get('header') |
|||
.findByRole('navigation', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.findByRole('button', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.as('userMenuButton') |
|||
|
|||
// Open the user menu
|
|||
cy.get('@userMenuButton').click() |
|||
cy.get('#header-menu-public-page-user-menu') |
|||
.as('headerMenu') |
|||
|
|||
cy.get('@headerMenu') |
|||
.findByRole('link', { name: /Set public name/i }) |
|||
.should('be.visible') |
|||
.click() |
|||
|
|||
// Check the dialog is visible
|
|||
cy.findByRole('dialog', { name: /Guest identification/i }) |
|||
.should('be.visible') |
|||
.as('guestIdentificationDialog') |
|||
|
|||
// Set the name
|
|||
cy.get('@guestIdentificationDialog') |
|||
.findByRole('textbox', { name: /Name/i }) |
|||
.should('be.visible') |
|||
.type('{selectAll}Jane Doe{enter}') |
|||
|
|||
// Check that the dialog is closed
|
|||
cy.get('@guestIdentificationDialog') |
|||
.should('not.exist') |
|||
|
|||
// Create another share
|
|||
cy.visit(secondPublicShareUrl) |
|||
|
|||
cy.get('header') |
|||
.findByRole('navigation', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.findByRole('button', { name: /User menu/i }) |
|||
.should('be.visible') |
|||
.as('userMenuButton') |
|||
|
|||
// Open the user menu
|
|||
cy.get('@userMenuButton').click() |
|||
cy.get('#header-menu-public-page-user-menu') |
|||
.as('headerMenu') |
|||
|
|||
// See the note with the current name
|
|||
cy.get('@headerMenu') |
|||
.findByRole('note') |
|||
.should('contain', 'You will be identified as Jane Doe') |
|||
|
|||
cy.get('@headerMenu') |
|||
.findByRole('link', { name: /Change public name/i }) |
|||
.should('be.visible') |
|||
.click() |
|||
|
|||
// Check the dialog is visible
|
|||
cy.findByRole('dialog', { name: /Guest identification/i }) |
|||
.should('be.visible') |
|||
.as('guestIdentificationDialog') |
|||
|
|||
// Check that the note states the current name
|
|||
// cy.get('@guestIdentificationDialog')
|
|||
// .findByRole('note')
|
|||
// .should('contain', 'are currently identified as Jane Doe')
|
|||
|
|||
// Change the name
|
|||
cy.get('@guestIdentificationDialog') |
|||
.findByRole('textbox', { name: /Name/i }) |
|||
.should('be.visible') |
|||
.type('{selectAll}Foo Bar{enter}') |
|||
|
|||
// Check that the dialog is closed
|
|||
cy.get('@guestIdentificationDialog') |
|||
.should('not.exist') |
|||
|
|||
// Check that the avatar changed with the second name
|
|||
cy.get('@userMenuButton') |
|||
.find('img') |
|||
.invoke('attr', 'src') |
|||
.should('include', 'avatar/guest/Foo%20Bar') |
|||
}) |
|||
}) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue