diff options
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagPlugin.php | 7 | ||||
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryCheckbox.vue | 1 | ||||
-rw-r--r-- | apps/files/src/components/FilesListTableHeader.vue | 2 | ||||
-rw-r--r-- | apps/files/src/components/FilesListTableHeaderActions.vue | 3 | ||||
-rw-r--r-- | apps/systemtags/src/components/SystemTagPicker.vue | 28 | ||||
-rw-r--r-- | apps/systemtags/src/event-bus.d.ts | 4 | ||||
-rw-r--r-- | apps/systemtags/src/files_actions/bulkSystemTagsAction.ts | 2 | ||||
-rw-r--r-- | cypress/e2e/files/FilesUtils.ts | 27 | ||||
-rw-r--r-- | cypress/e2e/systemtags/files-bulk-action.cy.ts | 354 | ||||
-rw-r--r-- | lib/private/SystemTag/SystemTagObjectMapper.php | 2 | ||||
-rw-r--r-- | lib/public/SystemTag/ISystemTagObjectMapper.php | 2 |
11 files changed, 414 insertions, 18 deletions
diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index c88a69f1154..00585953b29 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -21,7 +21,6 @@ use OCP\Util; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Conflict; use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Exception\UnsupportedMediaType; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; @@ -218,8 +217,8 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { $propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath())); } - $propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node): string|null { - return $node->getSystemTag()->getETag(); + $propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node): string { + return '"' . ($node->getSystemTag()->getETag() ?? '') . '"'; }); $propFind->handle(self::ID_PROPERTYNAME, function () use ($node) { @@ -379,7 +378,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { if (isset($props[self::OBJECTIDS_PROPERTYNAME])) { $propValue = $props[self::OBJECTIDS_PROPERTYNAME]; - if (!($propValue instanceof SystemTagsObjectList) || count($propValue?->getObjects() ?: []) === 0) { + if (!($propValue instanceof SystemTagsObjectList) || count($propValue->getObjects()) === 0) { throw new BadRequest('Invalid object-ids property'); } diff --git a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue index f2ab39d7525..9caa08cfe22 100644 --- a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue +++ b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue @@ -9,6 +9,7 @@ <NcCheckboxRadioSwitch v-else :aria-label="ariaLabel" :checked="isSelected" + data-cy-files-list-row-checkbox @update:checked="onSelectionChange" /> </td> </template> diff --git a/apps/files/src/components/FilesListTableHeader.vue b/apps/files/src/components/FilesListTableHeader.vue index e91cfa055c1..c8334abda5e 100644 --- a/apps/files/src/components/FilesListTableHeader.vue +++ b/apps/files/src/components/FilesListTableHeader.vue @@ -6,7 +6,7 @@ <tr class="files-list__row-head"> <th class="files-list__column files-list__row-checkbox" @keyup.esc.exact="resetSelection"> - <NcCheckboxRadioSwitch v-bind="selectAllBind" @update:checked="onToggleAll" /> + <NcCheckboxRadioSwitch v-bind="selectAllBind" data-cy-files-list-selection-checkbox @update:checked="onToggleAll" /> </th> <!-- Columns display --> diff --git a/apps/files/src/components/FilesListTableHeaderActions.vue b/apps/files/src/components/FilesListTableHeaderActions.vue index a5732220441..fa5f7d4bd5f 100644 --- a/apps/files/src/components/FilesListTableHeaderActions.vue +++ b/apps/files/src/components/FilesListTableHeaderActions.vue @@ -3,7 +3,7 @@ - SPDX-License-Identifier: AGPL-3.0-or-later --> <template> - <div class="files-list__column files-list__row-actions-batch"> + <div class="files-list__column files-list__row-actions-batch" data-cy-files-list-selection-actions> <NcActions ref="actionsMenu" container="#app-content-vue" :disabled="!!loading || areSomeNodesLoading" @@ -15,6 +15,7 @@ :key="action.id" :aria-label="action.displayName(nodes, currentView) + ' ' + t('files', '(selected)') /** TRANSLATORS: Selected like 'selected files and folders' */" :class="'files-list__row-actions-batch-' + action.id" + :data-cy-files-list-selection-action="action.id" @click="onActionClick(action)"> <template #icon> <NcLoadingIcon v-if="loading === action.id" :size="18" /> diff --git a/apps/systemtags/src/components/SystemTagPicker.vue b/apps/systemtags/src/components/SystemTagPicker.vue index c06bebc3bdc..732d509ac24 100644 --- a/apps/systemtags/src/components/SystemTagPicker.vue +++ b/apps/systemtags/src/components/SystemTagPicker.vue @@ -23,22 +23,28 @@ <!-- Search or create input --> <form class="systemtags-picker__create" @submit.stop.prevent="onNewTag"> <NcTextField :value.sync="input" - :label="t('systemtags', 'Search or create tag')"> + :label="t('systemtags', 'Search or create tag')" + data-cy-systemtags-picker-input> <TagIcon :size="20" /> </NcTextField> - <NcButton :disabled="status === Status.CREATING_TAG" native-type="submit"> + <NcButton :disabled="status === Status.CREATING_TAG" + native-type="submit" + data-cy-systemtags-picker-input-submit> {{ t('systemtags', 'Create tag') }} </NcButton> </form> <!-- Tags list --> - <div v-if="filteredTags.length > 0" class="systemtags-picker__tags"> + <div v-if="filteredTags.length > 0" + class="systemtags-picker__tags" + data-cy-systemtags-picker-tags> <NcCheckboxRadioSwitch v-for="tag in filteredTags" :key="tag.id" :label="tag.displayName" :checked="isChecked(tag)" :indeterminate="isIndeterminate(tag)" :disabled="!tag.canAssign" + :data-cy-systemtags-picker-tag="tag.id" @update:checked="onCheckUpdate(tag, $event)"> {{ formatTagName(tag) }} </NcCheckboxRadioSwitch> @@ -61,10 +67,15 @@ </template> <template #actions> - <NcButton :disabled="status !== Status.BASE" type="tertiary" @click="onCancel"> + <NcButton :disabled="status !== Status.BASE" + type="tertiary" + data-cy-systemtags-picker-button-cancel + @click="onCancel"> {{ t('systemtags', 'Cancel') }} </NcButton> - <NcButton :disabled="!hasChanges || status !== Status.BASE" @click="onSubmit"> + <NcButton :disabled="!hasChanges || status !== Status.BASE" + data-cy-systemtags-picker-button-submit + @click="onSubmit"> {{ t('systemtags', 'Apply changes') }} </NcButton> </template> @@ -270,11 +281,11 @@ export default defineComponent({ }, formatTagName(tag: TagWithId): string { - if (tag.userVisible) { + if (!tag.userVisible) { return t('systemtags', '{displayName} (hidden)', { displayName: tag.displayName }) } - if (tag.userAssignable) { + if (!tag.userAssignable) { return t('systemtags', '{displayName} (restricted)', { displayName: tag.displayName }) } @@ -317,6 +328,9 @@ export default defineComponent({ const tag = await fetchTag(id) this.tags.push(tag) this.input = '' + + // Check the newly created tag + this.onCheckUpdate(tag, true) } catch (error) { showError((error as Error)?.message || t('systemtags', 'Failed to create tag')) } finally { diff --git a/apps/systemtags/src/event-bus.d.ts b/apps/systemtags/src/event-bus.d.ts index 368d0acaf0f..4009f3f372b 100644 --- a/apps/systemtags/src/event-bus.d.ts +++ b/apps/systemtags/src/event-bus.d.ts @@ -1,3 +1,7 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ import type { Node } from '@nextcloud/files' declare module '@nextcloud/event-bus' { diff --git a/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts b/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts index 709a4c7ebcb..ad2ebab70c4 100644 --- a/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts +++ b/apps/systemtags/src/files_actions/bulkSystemTagsAction.ts @@ -19,7 +19,7 @@ export const action = new FileAction({ // If the app is disabled, the action is not available anyway enabled(nodes) { - if (nodes.length > 0) { + if (nodes.length === 0) { return false } diff --git a/cypress/e2e/files/FilesUtils.ts b/cypress/e2e/files/FilesUtils.ts index 0f2b1154200..182972ee44c 100644 --- a/cypress/e2e/files/FilesUtils.ts +++ b/cypress/e2e/files/FilesUtils.ts @@ -14,11 +14,15 @@ export const getActionButtonForFile = (filename: string) => getActionsForFile(fi export const triggerActionForFileId = (fileid: number, actionId: string) => { getActionButtonForFileId(fileid).click() - cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click() + // Getting the last button to avoid the one from popup fading out + cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).last() + .should('exist').click() } export const triggerActionForFile = (filename: string, actionId: string) => { getActionButtonForFile(filename).click() - cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click() + // Getting the last button to avoid the one from popup fading out + cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).last() + .should('exist').click() } export const triggerInlineActionForFileId = (fileid: number, actionId: string) => { @@ -28,6 +32,25 @@ export const triggerInlineActionForFile = (filename: string, actionId: string) = getActionsForFile(filename).get(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click() } +export const selectAllFiles = () => { + cy.get('[data-cy-files-list-selection-checkbox]').findByRole('checkbox').click({ force: true }) +} +export const selectRowForFile = (filename: string) => { + getRowForFile(filename) + .find('[data-cy-files-list-row-checkbox]') + .findByRole('checkbox') + .click({ force: true }) + .should('be.checked') + cy.get('[data-cy-files-list-selection-checkbox]').findByRole('checkbox').should('satisfy', (elements) => { + return elements.length === 1 && (elements[0].checked === true || elements[0].indeterminate === true) + }) + +} + +export const triggerSelectionAction = (actionId: string) => { + cy.get(`button[data-cy-files-list-selection-action="${CSS.escape(actionId)}"]`).should('exist').click() +} + export const moveFile = (fileName: string, dirPath: string) => { getRowForFile(fileName).should('be.visible') triggerActionForFile(fileName, 'move-copy') diff --git a/cypress/e2e/systemtags/files-bulk-action.cy.ts b/cypress/e2e/systemtags/files-bulk-action.cy.ts new file mode 100644 index 00000000000..bfc2280a9d8 --- /dev/null +++ b/cypress/e2e/systemtags/files-bulk-action.cy.ts @@ -0,0 +1,354 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { User } from '@nextcloud/cypress' +import { randomBytes } from 'crypto' +import { getRowForFile, selectAllFiles, selectRowForFile, triggerSelectionAction } from '../files/FilesUtils' +import { createShare } from '../files_sharing/FilesSharingUtils' + +let tags = {} as Record<string, number> +const files = [ + 'file1.txt', + 'file2.txt', + 'file3.txt', + 'file4.txt', + 'file5.txt', +] + +function resetTags() { + tags = {} + for (const tag in [0, 1, 2, 3, 4]) { + tags[randomBytes(8).toString('base64').slice(0, 6)] = 0 + } + + // delete any existing tags + cy.runOccCommand('tag:list --output=json').then((output) => { + Object.keys(JSON.parse(output.stdout)).forEach((id) => { + cy.runOccCommand(`tag:delete ${id}`) + }) + }) + + // create tags + Object.keys(tags).forEach((tag) => { + cy.runOccCommand(`tag:add ${tag} public --output=json`).then((output) => { + tags[tag] = JSON.parse(output.stdout).id as number + }) + }) + cy.log('Using tags', tags) +} + +function expectInlineTagForFile(file: string, tags: string[]) { + getRowForFile(file) + .find('[data-systemtags-fileid]') + .findAllByRole('listitem') + .should('have.length', tags.length) + .each(tag => { + expect(tag.text()).to.be.oneOf(tags) + }) +} + +function triggerTagManagementDialogAction() { + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/').as('getTagsList') + triggerSelectionAction('systemtags:bulk') + cy.wait('@getTagsList') + cy.get('[data-cy-systemtags-picker]').should('be.visible') +} + +describe('Systemtags: Files bulk action', { testIsolation: false }, () => { + let snapshot: string + let user1: User + let user2: User + + before(() => { + cy.createRandomUser().then((_user1) => { + user1 = _user1 + cy.createRandomUser().then((_user2) => { + user2 = _user2 + }) + + files.forEach((file) => { + cy.uploadContent(user1, new Blob([]), 'text/plain', '/' + file) + }) + }) + + resetTags() + }) + + it('Can assign tag to selection', () => { + cy.login(user1) + cy.visit('/apps/files') + + files.forEach((file) => { + getRowForFile(file).should('be.visible') + }) + selectRowForFile('file2.txt') + selectRowForFile('file4.txt') + + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData') + + const tag = Object.keys(tags)[3] + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData') + cy.wait('@assignTagData') + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file2.txt', [tag]) + expectInlineTagForFile('file4.txt', [tag]) + }) + + it('Can assign multiple tags to selection', () => { + cy.login(user1) + cy.visit('/apps/files') + + files.forEach((file) => { + getRowForFile(file).should('be.visible') + }) + selectAllFiles() + + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData') + + const prevTag = Object.keys(tags)[3] + const tag1 = Object.keys(tags)[1] + const tag2 = Object.keys(tags)[2] + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag1]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag2]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData') + cy.wait('@assignTagData') + cy.get('@getTagData.all').should('have.length', 2) + cy.get('@assignTagData.all').should('have.length', 2) + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file1.txt', [tag1, tag2]) + expectInlineTagForFile('file2.txt', [prevTag, tag1, tag2]) + expectInlineTagForFile('file3.txt', [tag1, tag2]) + expectInlineTagForFile('file4.txt', [prevTag, tag1, tag2]) + expectInlineTagForFile('file5.txt', [tag1, tag2]) + }) + + it('Can remove tag from selection', () => { + cy.login(user1) + cy.visit('/apps/files') + + files.forEach((file) => { + getRowForFile(file).should('be.visible') + }) + selectRowForFile('file1.txt') + selectRowForFile('file3.txt') + selectRowForFile('file4.txt') + + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData') + + const firstTag = Object.keys(tags)[3] + const tag1 = Object.keys(tags)[1] + const tag2 = Object.keys(tags)[2] + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag2]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData') + cy.wait('@assignTagData') + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file1.txt', [tag1]) + expectInlineTagForFile('file2.txt', [firstTag, tag1, tag2]) + expectInlineTagForFile('file3.txt', [tag1]) + expectInlineTagForFile('file4.txt', [firstTag, tag1]) + expectInlineTagForFile('file5.txt', [tag1, tag2]) + + }) + + it('Can remove multiple tags from selection', () => { + cy.login(user1) + cy.visit('/apps/files') + + files.forEach((file) => { + getRowForFile(file).should('be.visible') + }) + selectAllFiles() + + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData') + + cy.get('[data-cy-systemtags-picker-tag] input:indeterminate').should('exist') + .click({ force: true, multiple: true }) + // indeterminate became checked + cy.get('[data-cy-systemtags-picker-tag] input:checked').should('exist') + .click({ force: true, multiple: true }) + // now all are unchecked + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData') + cy.wait('@assignTagData') + cy.get('@getTagData.all').should('have.length', 3) + cy.get('@assignTagData.all').should('have.length', 3) + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file1.txt', []) + expectInlineTagForFile('file2.txt', []) + expectInlineTagForFile('file3.txt', []) + expectInlineTagForFile('file4.txt', []) + expectInlineTagForFile('file5.txt', []) + }) + + it('Can assign and remove multiple tags as a secondary user', () => { + // Create new users + cy.createRandomUser().then((_user1) => { + user1 = _user1 + cy.createRandomUser().then((_user2) => { + user2 = _user2 + }) + + files.forEach((file) => { + cy.uploadContent(user1, new Blob([]), 'text/plain', '/' + file) + }) + }) + + cy.login(user1) + cy.visit('/apps/files') + + files.forEach((file) => { + getRowForFile(file).should('be.visible') + }) + selectAllFiles() + + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData1') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData1') + + const tag1 = Object.keys(tags)[0] + const tag2 = Object.keys(tags)[3] + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag1]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag2]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData1') + cy.wait('@assignTagData1') + cy.get('@getTagData1.all').should('have.length', 2) + cy.get('@assignTagData1.all').should('have.length', 2) + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file1.txt', [tag1, tag2]) + expectInlineTagForFile('file2.txt', [tag1, tag2]) + expectInlineTagForFile('file3.txt', [tag1, tag2]) + expectInlineTagForFile('file4.txt', [tag1, tag2]) + expectInlineTagForFile('file5.txt', [tag1, tag2]) + + createShare('file1.txt', user2.userId) + createShare('file3.txt', user2.userId) + + cy.login(user2) + cy.visit('/apps/files') + + getRowForFile('file1.txt').should('be.visible') + getRowForFile('file3.txt').should('be.visible') + + expectInlineTagForFile('file1.txt', [tag1, tag2]) + expectInlineTagForFile('file3.txt', [tag1, tag2]) + + selectRowForFile('file1.txt') + selectRowForFile('file3.txt') + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData2') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData2') + + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag1]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get(`[data-cy-systemtags-picker-tag=${tags[tag2]}]`).should('be.visible') + .findByRole('checkbox').click({ force: true }) + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData2') + cy.wait('@assignTagData2') + cy.get('@getTagData2.all').should('have.length', 2) + cy.get('@assignTagData2.all').should('have.length', 2) + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file1.txt', []) + expectInlineTagForFile('file3.txt', []) + + cy.login(user1) + cy.visit('/apps/files') + + expectInlineTagForFile('file1.txt', []) + expectInlineTagForFile('file3.txt', []) + }) + + it('Can create tag and assign files to it', () => { + cy.createRandomUser().then((user1) => { + files.forEach((file) => { + cy.uploadContent(user1, new Blob([]), 'text/plain', '/' + file) + }) + + cy.login(user1) + cy.visit('/apps/files') + + files.forEach((file) => { + getRowForFile(file).should('be.visible') + }) + selectAllFiles() + + triggerTagManagementDialogAction() + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 5) + + cy.intercept('POST', '/remote.php/dav/systemtags').as('createTag') + cy.intercept('PROPFIND', '/remote.php/dav/systemtags/*/files').as('getTagData') + cy.intercept('PROPPATCH', '/remote.php/dav/systemtags/*/files').as('assignTagData') + + const newTag = randomBytes(8).toString('base64').slice(0, 6) + cy.get('[data-cy-systemtags-picker-input]').type(newTag) + cy.get('[data-cy-systemtags-picker-input-submit]').click() + + cy.wait('@createTag') + cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 6) + // Verify the new tag is selected by default + cy.get('[data-cy-systemtags-picker-tag]').contains(newTag) + .parents('[data-cy-systemtags-picker-tag]') + .findByRole('checkbox', { hidden: true }).should('be.checked') + + // Apply changes + cy.get('[data-cy-systemtags-picker-button-submit]').click() + + cy.wait('@getTagData') + cy.wait('@assignTagData') + cy.get('@getTagData.all').should('have.length', 1) + cy.get('@assignTagData.all').should('have.length', 1) + cy.get('[data-cy-systemtags-picker]').should('not.exist') + + expectInlineTagForFile('file1.txt', [newTag]) + expectInlineTagForFile('file2.txt', [newTag]) + expectInlineTagForFile('file3.txt', [newTag]) + expectInlineTagForFile('file4.txt', [newTag]) + expectInlineTagForFile('file5.txt', [newTag]) + }) + }) +}) diff --git a/lib/private/SystemTag/SystemTagObjectMapper.php b/lib/private/SystemTag/SystemTagObjectMapper.php index ddb04fa968d..09a7ce1a6ed 100644 --- a/lib/private/SystemTag/SystemTagObjectMapper.php +++ b/lib/private/SystemTag/SystemTagObjectMapper.php @@ -204,7 +204,7 @@ class SystemTagObjectMapper implements ISystemTagObjectMapper { /** * Update the etag for the given tags. * - * @param int[] $tagIds + * @param string[] $tagIds */ private function updateEtagForTags(array $tagIds): void { // Update etag after assigning tags diff --git a/lib/public/SystemTag/ISystemTagObjectMapper.php b/lib/public/SystemTag/ISystemTagObjectMapper.php index 96e8c1e848a..c604fa93c58 100644 --- a/lib/public/SystemTag/ISystemTagObjectMapper.php +++ b/lib/public/SystemTag/ISystemTagObjectMapper.php @@ -129,7 +129,7 @@ interface ISystemTagObjectMapper { * @param string $tagId tag id * @param string $objectType object type * @param string[] $objectIds list of object ids - * + * * @throws TagNotFoundException if the tag does not exist * @since 31.0.0 */ |