From c23fb8e09da6d1025d51e4d3a03900bd484c3416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Herbaux?= Date: Thu, 17 Oct 2024 10:04:52 +0200 Subject: [PATCH 1/6] Sort Generated Content-Types and Components Definitions (#21868) --- .../typescript/lib/generators/common/imports.js | 6 +++--- .../lib/generators/common/models/schema.js | 8 +++++--- .../lib/generators/common/models/utils.js | 2 +- .../typescript/lib/generators/components/index.js | 13 +++++++++---- .../lib/generators/content-types/index.js | 13 +++++++++---- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/utils/typescript/lib/generators/common/imports.js b/packages/utils/typescript/lib/generators/common/imports.js index 9d1311ecf4f..e326010b2e5 100644 --- a/packages/utils/typescript/lib/generators/common/imports.js +++ b/packages/utils/typescript/lib/generators/common/imports.js @@ -18,9 +18,9 @@ module.exports = { }, generateImportDefinition() { - const formattedImports = imports.map((key) => - factory.createImportSpecifier(false, undefined, factory.createIdentifier(key)) - ); + const formattedImports = imports + .sort() + .map((key) => factory.createImportSpecifier(false, undefined, factory.createIdentifier(key))); return [ factory.createImportDeclaration( diff --git a/packages/utils/typescript/lib/generators/common/models/schema.js b/packages/utils/typescript/lib/generators/common/models/schema.js index 83a12ef6fa2..7660d47c03e 100644 --- a/packages/utils/typescript/lib/generators/common/models/schema.js +++ b/packages/utils/typescript/lib/generators/common/models/schema.js @@ -22,9 +22,11 @@ const { addImport } = require('../imports'); const generateAttributePropertySignature = (schema) => { const { attributes } = schema; - const properties = Object.entries(attributes).map(([attributeName, attribute]) => { - return attributeToPropertySignature(schema, attributeName, attribute); - }); + const properties = Object.entries(attributes) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([attributeName, attribute]) => { + return attributeToPropertySignature(schema, attributeName, attribute); + }); return factory.createPropertySignature( undefined, diff --git a/packages/utils/typescript/lib/generators/common/models/utils.js b/packages/utils/typescript/lib/generators/common/models/utils.js index a27d3d193c5..cc4bcb8a125 100644 --- a/packages/utils/typescript/lib/generators/common/models/utils.js +++ b/packages/utils/typescript/lib/generators/common/models/utils.js @@ -111,7 +111,7 @@ const toTypeLiteral = (data) => { throw new Error(`Cannot convert to object literal. Unknown type "${typeof data}"`); } - const entries = Object.entries(data); + const entries = Object.entries(data).sort((a, b) => a[0].localeCompare(b[0])); const props = entries.reduce((acc, [key, value]) => { // Handle keys such as content-type-builder & co. diff --git a/packages/utils/typescript/lib/generators/components/index.js b/packages/utils/typescript/lib/generators/components/index.js index 3f42a70962b..6ae299549e3 100644 --- a/packages/utils/typescript/lib/generators/components/index.js +++ b/packages/utils/typescript/lib/generators/components/index.js @@ -1,6 +1,7 @@ 'use strict'; const { factory } = require('typescript'); +const { pipe, values, sortBy, map } = require('lodash/fp'); const { models } = require('../common'); const { emitDefinitions, format, generateSharedExtensionDefinition } = require('../utils'); @@ -23,10 +24,14 @@ const generateComponentsDefinitions = async (options = {}) => { const { components } = strapi; - const componentsDefinitions = Object.values(components).map((contentType) => ({ - uid: contentType.uid, - definition: models.schema.generateSchemaDefinition(contentType), - })); + const componentsDefinitions = pipe( + values, + sortBy('uid'), + map((component) => ({ + uid: component.uid, + definition: models.schema.generateSchemaDefinition(component), + })) + )(components); options.logger.debug(`Found ${componentsDefinitions.length} components.`); diff --git a/packages/utils/typescript/lib/generators/content-types/index.js b/packages/utils/typescript/lib/generators/content-types/index.js index f77181dd9c4..70cb89badb5 100644 --- a/packages/utils/typescript/lib/generators/content-types/index.js +++ b/packages/utils/typescript/lib/generators/content-types/index.js @@ -1,6 +1,7 @@ 'use strict'; const { factory } = require('typescript'); +const { values, pipe, map, sortBy } = require('lodash/fp'); const { models } = require('../common'); const { emitDefinitions, format, generateSharedExtensionDefinition } = require('../utils'); @@ -23,10 +24,14 @@ const generateContentTypesDefinitions = async (options = {}) => { const { contentTypes } = strapi; - const contentTypesDefinitions = Object.values(contentTypes).map((contentType) => ({ - uid: contentType.uid, - definition: models.schema.generateSchemaDefinition(contentType), - })); + const contentTypesDefinitions = pipe( + values, + sortBy('uid'), + map((contentType) => ({ + uid: contentType.uid, + definition: models.schema.generateSchemaDefinition(contentType), + })) + )(contentTypes); options.logger.debug(`Found ${contentTypesDefinitions.length} content-types.`); From 4cf36024d8e6011c265fdc01924d063589432f8f Mon Sep 17 00:00:00 2001 From: Jamie Howard <48524071+jhoward1994@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:19:03 +0100 Subject: [PATCH 2/6] fix(e2e): remove webkit skip conditions (#21611) * fix(e2e): remove webkit skip conditions * chore: revert uniqueness spec to test others on ci * fix(e2e): ctb navigation * chore: ci config to speed up testing * chore: revert lock * chore(e2e): wip add webkit waiting * chore: single out history spec changes for ci * chore(e2e): use new webkit navigation for all affected test files * chore(e2e): revert e2e ci config so all tests run again * chore(e2e): temp skip uniqueness to test others * chore(e2e): revert so all tests run again * chore(e2e): re-skip uniqueness in webkit * chore(e2e): revert workflow yaml * chore(e2e): pr feedback --------- Co-authored-by: Ben Irvin --- .../e2e/tests/content-manager/history.spec.ts | 115 ++++++++---------- .../tests/content-manager/uniqueness.spec.ts | 50 ++++---- .../create-collection-type.spec.ts | 18 ++- .../single-type/create-single-type.spec.ts | 18 ++- tests/e2e/tests/i18n/editview.spec.ts | 10 -- tests/e2e/utils/shared.ts | 14 +++ 6 files changed, 107 insertions(+), 118 deletions(-) diff --git a/tests/e2e/tests/content-manager/history.spec.ts b/tests/e2e/tests/content-manager/history.spec.ts index cf14da79c84..8d5efa63595 100644 --- a/tests/e2e/tests/content-manager/history.spec.ts +++ b/tests/e2e/tests/content-manager/history.spec.ts @@ -1,7 +1,7 @@ import { test, expect, Page } from '@playwright/test'; import { login } from '../../utils/login'; import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import'; -import { describeOnCondition, findAndClose } from '../../utils/shared'; +import { clickAndWait, describeOnCondition, findAndClose, skipCtbTour } from '../../utils/shared'; import { resetFiles } from '../../utils/file-reset'; import { waitForRestart } from '../../utils/restart'; @@ -37,6 +37,11 @@ const goToHistoryPage = async (page: Page) => { } }; +const goToContentTypeBuilder = async (page: Page) => { + await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' })); + await skipCtbTour(page); +}; + describeOnCondition(edition === 'EE')('History', () => { test.beforeEach(async ({ page }) => { await resetDatabaseAndImportDataFromPath('with-admin.tar', (cts) => cts, { coreStore: false }); @@ -52,8 +57,8 @@ describeOnCondition(edition === 'EE')('History', () => { }); test('A user should be able to restore a history version', async ({ page }) => { - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: /Create new entry/, exact: true }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: /Create new entry/, exact: true })); await page.waitForURL(ARTICLE_CREATE_URL); const titleInput = page.getByRole('textbox', { name: 'title' }); @@ -87,10 +92,10 @@ describeOnCondition(edition === 'EE')('History', () => { page, }) => { // Navigate to the content-manager - collection type - article - await page.getByRole('link', { name: 'Content Manager' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); await page.getByRole('combobox', { name: 'Select a locale' }).click(); await page.getByRole('option', { name: 'French (fr)' }).click(); - await page.getByRole('link', { name: /Create new entry/, exact: true }).click(); + await clickAndWait(page, page.getByRole('link', { name: /Create new entry/, exact: true })); await page.waitForURL(ARTICLE_CREATE_URL); /** @@ -111,8 +116,8 @@ describeOnCondition(edition === 'EE')('History', () => { // Go back to the CM to create a new english entry await page.goto('/admin'); - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: /Create new entry/, exact: true }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: /Create new entry/, exact: true })); await page.waitForURL(ARTICLE_CREATE_URL); // Create an english version @@ -139,7 +144,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(page.getByText(frenchTitle)).not.toBeVisible(); // Go back to the entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); /** * Update @@ -160,7 +165,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(previousVersion.getByText('(current)')).not.toBeVisible(); // Go back to the entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); /** * Publish @@ -178,7 +183,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(titleInput).toHaveValue('Being from Kansas City'); // Go back to the entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); /** * Modified @@ -195,28 +200,20 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(titleInput).toHaveValue('Being from Kansas City, Missouri'); }); - test('A user should see the relations and whether some are missing', async ({ - page, - browserName, - }) => { - // TODO: there is a webkit bug to be fixed - if (browserName === 'webkit') { - return test.fixme(); - } - + test('A user should see the relations and whether some are missing', async ({ page }) => { // Create new author - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Author' }).click(); - await page.getByRole('link', { name: /Create new entry/, exact: true }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: 'Author' })); + await clickAndWait(page, page.getByRole('link', { name: /Create new entry/, exact: true })); await page.waitForURL(AUTHOR_CREATE_URL); await page.getByRole('textbox', { name: 'name' }).fill('Will Kitman'); await page.getByRole('button', { name: 'Save' }).click(); await page.waitForURL(AUTHOR_EDIT_URL); // Create new article and add authors to it - await page.getByRole('link', { name: 'Article' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Article' })); await page.waitForURL(ARTICLE_LIST_URL); - await page.getByRole('link', { name: 'Create new entry' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Create new entry' })); await page.getByRole('textbox', { name: 'title' }).fill('Zava retires'); await page.getByRole('combobox', { name: 'Authors' }).click(); await page.getByText('Will Kitman').click(); @@ -226,14 +223,14 @@ describeOnCondition(edition === 'EE')('History', () => { await page.waitForURL(ARTICLE_EDIT_URL); // Delete one of the authors, leaving only Coach Beard - await page.getByRole('link', { name: 'Will Kitman' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Will Kitman' })); await page.waitForURL(AUTHOR_EDIT_URL); await page.getByRole('button', { name: /more actions/i }).click(); await page.getByRole('menuitem', { name: /delete entry/i }).click(); await page.getByRole('button', { name: /confirm/i }).click(); // Go to the the article's history page - await page.getByRole('link', { name: 'Article' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Article' })); await page.getByRole('gridcell', { name: 'Zava retires' }).click(); await page.waitForURL(ARTICLE_EDIT_URL); await goToHistoryPage(page); @@ -251,8 +248,8 @@ describeOnCondition(edition === 'EE')('History', () => { /** * Create an initial entry to also create an initial version */ - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: /Create new entry/, exact: true }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: /Create new entry/, exact: true })); await page.waitForURL(ARTICLE_CREATE_URL); await page.getByRole('textbox', { name: 'title' }).fill('Being from Kansas'); await page.getByRole('textbox', { name: 'slug' }).fill('being-from-kansas'); @@ -262,9 +259,7 @@ describeOnCondition(edition === 'EE')('History', () => { /** * Rename field in content-type builder */ - await page.getByRole('link', { name: 'Content-Type Builder' }).click(); - await page.waitForURL('**/content-type-builder'); - await page.getByRole('link', { name: 'Article' }).click(); + await goToContentTypeBuilder(page); await page.waitForURL( '/admin/plugins/content-type-builder/content-types/api::article.article' ); @@ -279,8 +274,8 @@ describeOnCondition(edition === 'EE')('History', () => { * Update the existing entry to create another version */ await page.goto('/admin'); - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Article' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: 'Article' })); await page.getByRole('gridcell', { name: 'being-from-kansas' }).click(); await page.waitForURL(ARTICLE_EDIT_URL); await page.getByRole('textbox', { name: 'titleRename' }).fill('Being from Kansas City'); @@ -316,8 +311,8 @@ describeOnCondition(edition === 'EE')('History', () => { /\/admin\/content-manager\/single-types\/api::homepage.homepage\/history(\?.*)?/; // Navigate to the content-manager - single type - homepage - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Homepage' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.getByRole('combobox', { name: 'Locales' }).click(); await page.getByRole('option', { name: 'French (fr)' }).click(); @@ -335,7 +330,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(titleInput).toHaveValue(frenchTitle); // Go back to the CM to create a new english entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); await page.getByRole('combobox', { name: 'Locales' }).click(); await page.getByRole('option', { name: 'English (en)' }).click(); @@ -362,7 +357,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(page.getByText(frenchTitle)).not.toBeVisible(); // Go back to the entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); /** * Update @@ -380,7 +375,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(titleInput).toHaveValue('AFC Richmond'); // Go back to the entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); /** * Publish @@ -399,7 +394,7 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(titleInput).toHaveValue('Welcome to AFC Richmond'); // Go back to the entry - await page.getByRole('link', { name: 'Back' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Back' })); /** * Modified @@ -415,18 +410,11 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(currentVersion.getByText('Modified')).toBeVisible(); }); - test('A user should see the relations and whether some are missing', async ({ - page, - browserName, - }) => { - // TODO: there is a webkit bug to be fixed - if (browserName === 'webkit') { - return test.fixme(); - } - + test('A user should see the relations and whether some are missing', async ({ page }) => { // Create relation in Content-Type Builder - await page.getByRole('link', { name: 'Content-Type Builder' }).click(); - await page.getByRole('link', { name: 'Homepage' }).click(); + await goToContentTypeBuilder(page); + + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.waitForURL( '/admin/plugins/content-type-builder/content-types/api::homepage.homepage' ); @@ -441,16 +429,18 @@ describeOnCondition(edition === 'EE')('History', () => { await expect(page.getByRole('cell', { name: 'authors', exact: true })).toBeVisible(); // Create new author - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Author' }).click(); - await page.getByRole('link', { name: /Create new entry/, exact: true }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + // await page.waitForSelector('text=Author'); + await clickAndWait(page, page.getByRole('link', { name: 'Author' })); + // await page.waitForSelector('text=Create new entry'); + await clickAndWait(page, page.getByRole('link', { name: /Create new entry/, exact: true })); await page.waitForURL(AUTHOR_CREATE_URL); await page.getByRole('textbox', { name: 'name' }).fill('Will Kitman'); await page.getByRole('button', { name: 'Save' }).click(); await page.waitForURL(AUTHOR_EDIT_URL); // Add author to homepage - await page.getByRole('link', { name: 'Homepage' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.waitForURL(HOMEPAGE_EDIT_URL); await page.getByRole('combobox', { name: 'Authors' }).click(); await page.getByText('Will Kitman').click(); @@ -459,14 +449,14 @@ describeOnCondition(edition === 'EE')('History', () => { await page.getByRole('button', { name: 'Save' }).click(); // Delete one of the authors, leaving only Coach Beard - await page.getByRole('link', { name: 'Will Kitman' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Will Kitman' })); await page.waitForURL(AUTHOR_EDIT_URL); await page.getByRole('button', { name: /more actions/i }).click(); await page.getByRole('menuitem', { name: /delete entry/i }).click(); await page.getByRole('button', { name: /confirm/i }).click(); // Go to the the article's history page - await page.getByRole('link', { name: 'Homepage' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.waitForURL(HOMEPAGE_EDIT_URL); await page.getByRole('button', { name: /more actions/i }).click(); await page.getByRole('menuitem', { name: /content history/i }).click(); @@ -484,8 +474,8 @@ describeOnCondition(edition === 'EE')('History', () => { /** * Create an initial entry to also create an initial version */ - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Homepage' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.getByRole('textbox', { name: 'title' }).fill('Welcome to AFC Richmond'); await page.getByRole('button', { name: 'Save' }).click(); await findAndClose(page, 'Saved Document'); @@ -493,10 +483,9 @@ describeOnCondition(edition === 'EE')('History', () => { /** * Rename field in content-type builder */ - await page.getByRole('link', { name: 'Content-Type Builder' }).click(); - await page.waitForURL('**/content-type-builder'); + await goToContentTypeBuilder(page); - await page.getByRole('link', { name: 'Homepage' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.waitForURL( '/admin/plugins/content-type-builder/content-types/api::homepage.homepage' ); @@ -511,8 +500,8 @@ describeOnCondition(edition === 'EE')('History', () => { * Update the existing entry to create another version */ await page.goto('/admin'); - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Homepage' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: 'Homepage' })); await page.getByRole('textbox', { name: 'titleRename' }).fill('Welcome to AFC Richmond!'); await page.getByRole('button', { name: 'Save' }).click(); await findAndClose(page, 'Saved Document'); diff --git a/tests/e2e/tests/content-manager/uniqueness.spec.ts b/tests/e2e/tests/content-manager/uniqueness.spec.ts index 8568d77f3b7..98f90a899f1 100644 --- a/tests/e2e/tests/content-manager/uniqueness.spec.ts +++ b/tests/e2e/tests/content-manager/uniqueness.spec.ts @@ -1,7 +1,7 @@ import { test, expect, Page } from '@playwright/test'; import { login } from '../../utils/login'; import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import'; -import { findAndClose } from '../../utils/shared'; +import { findAndClose, clickAndWait } from '../../utils/shared'; type Field = { name: string; @@ -20,8 +20,8 @@ test.describe('Uniqueness', () => { await page.goto('/admin'); await login({ page }); - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page.getByRole('link', { name: 'Unique' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' })); + await clickAndWait(page, page.getByRole('link', { name: 'Unique' })); }); const SCALAR_FIELDS: Field[] = [ @@ -101,8 +101,8 @@ test.describe('Uniqueness', () => { const clickSave = async (page: Page) => { await page.getByRole('button', { name: 'Save' }).isEnabled(); - await page.getByRole('tab', { name: 'Draft' }).click(); - await page.getByRole('button', { name: 'Save' }).click(); + await clickAndWait(page, page.getByRole('tab', { name: 'Draft' })); + await clickAndWait(page, page.getByRole('button', { name: 'Save' })); await expect(page.getByText('Saved document')).toBeVisible(); }; @@ -114,20 +114,20 @@ test.describe('Uniqueness', () => { // testing against if (isSingle) { - await page.getByRole('button', { name: 'No entry yet. Click' }).first().click(); - await page.getByRole('button', { name: 'No entry yet. Click' }).first().click(); + await clickAndWait(page, page.getByRole('button', { name: 'No entry yet. Click' }).first()); + await clickAndWait(page, page.getByRole('button', { name: 'No entry yet. Click' }).first()); } else { - await page.getByRole('button', { name: 'No entry yet. Click' }).nth(1).click(); - await page - .getByLabel('', { exact: true }) - .getByRole('button', { name: 'No entry yet. Click' }) - .click(); + await clickAndWait(page, page.getByRole('button', { name: 'No entry yet. Click' }).nth(1)); + await clickAndWait( + page, + page.getByLabel('', { exact: true }).getByRole('button', { name: 'No entry yet. Click' }) + ); } } }; const createNewEntry = async (page: Page, url: RegExp) => { - await page.getByRole('link', { name: 'Create new entry' }).first().click(); + await clickAndWait(page, page.getByRole('link', { name: 'Create new entry' }).first()); await page.waitForURL(url); }; @@ -137,22 +137,22 @@ test.describe('Uniqueness', () => { }; const publishDocument = async (page: Page) => { - await page.getByRole('button', { name: 'Publish' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Publish' })); await expect(page.getByText('Published document')).toBeVisible(); }; const navigateToListView = async (page: Page) => { - await page.getByRole('link', { name: 'Unique' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Unique' })); if (await page.getByText('Confirmation').isVisible()) { - await page.getByRole('button', { name: 'Confirm' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Confirm' })); } await page.waitForURL(LIST_URL); }; const changeLocale = async (page: Page, locale: string) => { - await page.getByRole('combobox', { name: 'Select a locale' }).click(); - await page.getByText(locale).click(); + await clickAndWait(page, page.getByRole('combobox', { name: 'Select a locale' })); + await clickAndWait(page, page.getByText(locale)); }; /** @@ -179,6 +179,7 @@ test.describe('Uniqueness', () => { if (browserName === 'webkit') { return test.fixme(); } + await createNewEntry(page, CREATE_URL); const fieldRole = 'role' in field ? field.role : 'textbox'; @@ -187,17 +188,20 @@ test.describe('Uniqueness', () => { if (isRepeatableComponentField) { // If the field is a repeatable component field, we add an entry and fill // it with the same value to test uniqueness within the same entity. - await page.getByRole('button', { name: 'Add an entry' }).click(); - await page.getByRole('region').getByRole('button', { name: 'No entry yet. Click' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Add an entry' })); + await clickAndWait( + page, + page.getByRole('region').getByRole('button', { name: 'No entry yet. Click' }) + ); await page.getByRole(fieldRole, { name: field.name }).fill(field.value); await clickSave(page); await findAndClose(page, 'Saved document'); - await page.getByRole('button', { name: 'Publish' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Publish' })); await expect(page.getByText('Warning:2 errors occurred')).toBeVisible(); - await page.getByRole('button', { name: 'Delete' }).nth(1).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Delete' }).nth(1)); } await clickSave(page); @@ -222,7 +226,7 @@ test.describe('Uniqueness', () => { await clickSave(page); await findAndClose(page, 'Saved document'); - await page.getByRole('button', { name: 'Publish' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Publish' })); await expect(page.getByText('Warning:This attribute must be unique')).toBeVisible(); await navigateToListView(page); diff --git a/tests/e2e/tests/content-type-builder/collection-type/create-collection-type.spec.ts b/tests/e2e/tests/content-type-builder/collection-type/create-collection-type.spec.ts index cfd55fedb82..57ce05566e7 100644 --- a/tests/e2e/tests/content-type-builder/collection-type/create-collection-type.spec.ts +++ b/tests/e2e/tests/content-type-builder/collection-type/create-collection-type.spec.ts @@ -3,6 +3,7 @@ import { login } from '../../../utils/login'; import { resetDatabaseAndImportDataFromPath } from '../../../utils/dts-import'; import { waitForRestart } from '../../../utils/restart'; import { resetFiles } from '../../../utils/file-reset'; +import { clickAndWait } from '../../../utils/shared'; test.describe('Create collection type', () => { // very long timeout for these tests because they restart the server multiple times @@ -14,7 +15,7 @@ test.describe('Create collection type', () => { await page.goto('/admin'); await login({ page }); - await page.getByRole('link', { name: 'Content-Type Builder' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' })); // close the tutorial modal if it's visible const modal = page.getByRole('button', { name: 'Close' }); @@ -31,7 +32,7 @@ test.describe('Create collection type', () => { }); test('Can create a collection type', async ({ page, browserName }) => { - await page.getByRole('button', { name: 'Create new collection type' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Create new collection type' })); await expect(page.getByRole('heading', { name: 'Create a collection type' })).toBeVisible(); @@ -44,20 +45,15 @@ test.describe('Create collection type', () => { const pluralId = page.getByLabel('API ID (Plural)'); await expect(pluralId).toHaveValue('secret-documents'); - await page.getByRole('button', { name: 'Continue' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Continue' })); await expect(page.getByText('Select a field for your collection type')).toBeVisible(); - // TODO: fix the bug that causes webkit location to be off - if (browserName === 'webkit') { - return test.fixme(); - } - - await page.getByText('Small or long text').click(); + await clickAndWait(page, page.getByText('Small or long text')); await page.getByLabel('Name', { exact: true }).fill('myattribute'); - await page.getByRole('button', { name: 'Finish' }).click(); - await page.getByRole('button', { name: 'Save' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Finish' })); + await clickAndWait(page, page.getByRole('button', { name: 'Save' })); await waitForRestart(page); diff --git a/tests/e2e/tests/content-type-builder/single-type/create-single-type.spec.ts b/tests/e2e/tests/content-type-builder/single-type/create-single-type.spec.ts index f6af27aa5a2..b638a6d22cd 100644 --- a/tests/e2e/tests/content-type-builder/single-type/create-single-type.spec.ts +++ b/tests/e2e/tests/content-type-builder/single-type/create-single-type.spec.ts @@ -3,6 +3,7 @@ import { login } from '../../../utils/login'; import { resetDatabaseAndImportDataFromPath } from '../../../utils/dts-import'; import { waitForRestart } from '../../../utils/restart'; import { resetFiles } from '../../../utils/file-reset'; +import { clickAndWait } from '../../../utils/shared'; test.describe('Create collection type', () => { // very long timeout for these tests because they restart the server multiple times @@ -14,7 +15,7 @@ test.describe('Create collection type', () => { await page.goto('/admin'); await login({ page }); - await page.getByRole('link', { name: 'Content-Type Builder' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' })); // close the tutorial modal if it's visible const modal = page.getByRole('button', { name: 'Close' }); @@ -31,7 +32,7 @@ test.describe('Create collection type', () => { }); test('Can create a single type', async ({ page, browserName }) => { - await page.getByRole('button', { name: 'Create new single type' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Create new single type' })); await expect(page.getByRole('heading', { name: 'Create a single type' })).toBeVisible(); @@ -44,20 +45,15 @@ test.describe('Create collection type', () => { const pluralId = page.getByLabel('API ID (Plural)'); await expect(pluralId).toHaveValue('secret-documents'); - await page.getByRole('button', { name: 'Continue' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Continue' })); await expect(page.getByText('Select a field for your single type')).toBeVisible(); - // TODO: fix the bug that causes webkit location to be off - if (browserName === 'webkit') { - return test.fixme(); - } - - await page.getByText('Small or long text').click(); + await clickAndWait(page, page.getByText('Small or long text')); await page.getByLabel('Name', { exact: true }).fill('myattribute'); - await page.getByRole('button', { name: 'Finish' }).click(); - await page.getByRole('button', { name: 'Save' }).click(); + await clickAndWait(page, page.getByRole('button', { name: 'Finish' })); + await clickAndWait(page, page.getByRole('button', { name: 'Save' })); await waitForRestart(page); diff --git a/tests/e2e/tests/i18n/editview.spec.ts b/tests/e2e/tests/i18n/editview.spec.ts index 45d86c6c131..1e0c4b7db68 100644 --- a/tests/e2e/tests/i18n/editview.spec.ts +++ b/tests/e2e/tests/i18n/editview.spec.ts @@ -370,11 +370,6 @@ test.describe('Edit view', () => { }); test('As a user I want to publish multiple locales of my document', async ({ page, browser }) => { - if (browser.browserType().name() === 'webkit') { - // See DX-1550 - return test.fixme(); - } - const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/; const EDIT_URL = /\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/; @@ -461,11 +456,6 @@ test.describe('Edit view', () => { page, browser, }) => { - if (browser.browserType().name() === 'webkit') { - // See DX-1550 - return test.fixme(); - } - const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/; const EDIT_URL = /\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/; diff --git a/tests/e2e/utils/shared.ts b/tests/e2e/utils/shared.ts index 59d72b21d6e..c128e2b594b 100644 --- a/tests/e2e/utils/shared.ts +++ b/tests/e2e/utils/shared.ts @@ -75,6 +75,20 @@ export const skipCtbTour = async (page: Page) => { } }; +/** + * Clicks on a link and waits for the page to load completely. + * + * NOTE: this util is used to avoid inconsistent behaviour on webkit + * + */ +export const clickAndWait = async (page: Page, locator: Locator) => { + await locator.click(); + + if (page.context().browser()?.browserType().name() === 'webkit') { + await page.waitForLoadState('networkidle'); + } +}; + /** * Look for an element containing text, and then click a sibling close button */ From 42b1f4024ffacb50505febfe826697570521b05e Mon Sep 17 00:00:00 2001 From: Andrei Luca <1881266+iamandrewluca@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:08:22 +0300 Subject: [PATCH 3/6] fix(strapi/types): add Attribute column type (#21858) --- .../core/types/src/schema/attribute/base.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/core/types/src/schema/attribute/base.ts b/packages/core/types/src/schema/attribute/base.ts index cc14033f682..65b73fd50ee 100644 --- a/packages/core/types/src/schema/attribute/base.ts +++ b/packages/core/types/src/schema/attribute/base.ts @@ -64,6 +64,27 @@ export interface Attribute { * Meaning that, if it's set to 'true', the attribute would be considered while performing a search operation. */ searchable?: boolean; + + /** + * Database validations and settings + * https://docs.strapi.io/dev-docs/backend-customization/models#database-validations-and-settings + * + * @experimental + * @deprecated The column property is experimental and can be deprecated/changed at any time in the future. + */ + column?: Partial; +} + +// NOTE: Copied directly from @strapi/database package +export interface Column { + type?: string; + name?: string; + args?: unknown[]; + defaultTo?: unknown; + notNullable?: boolean; + unsigned?: boolean; + unique?: boolean; + primary?: boolean; } /** From 8aa515fdcd2496a89aef717404ad77f8bb244aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Juvigny?= <8087692+remidej@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:05:31 +0200 Subject: [PATCH 4/6] chore: add preview tracking (#21913) --- .../core/admin/admin/src/features/Tracking.tsx | 1 + .../src/preview/components/PreviewSidePanel.tsx | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/core/admin/admin/src/features/Tracking.tsx b/packages/core/admin/admin/src/features/Tracking.tsx index 8ff5dea914f..df6f4eba433 100644 --- a/packages/core/admin/admin/src/features/Tracking.tsx +++ b/packages/core/admin/admin/src/features/Tracking.tsx @@ -186,6 +186,7 @@ interface EventWithoutProperties { | 'willSaveComponent' | 'willSaveContentType' | 'willSaveContentTypeLayout' + | 'willOpenPreview' | 'didEditFieldNameOnContentType' | 'didCreateRelease'; properties?: never; diff --git a/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx b/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx index 7897f8dcda6..09be0e96812 100644 --- a/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx +++ b/packages/core/content-manager/admin/src/preview/components/PreviewSidePanel.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { useClipboard, useNotification } from '@strapi/admin/strapi-admin'; +import { useClipboard, useNotification, useTracking } from '@strapi/admin/strapi-admin'; import { Button, Flex, IconButton } from '@strapi/design-system'; import { Link as LinkIcon } from '@strapi/icons'; import { UID } from '@strapi/types'; @@ -15,6 +15,7 @@ const PreviewSidePanel: PanelComponent = ({ model, documentId, document }) => { const { formatMessage } = useIntl(); const { toggleNotification } = useNotification(); const { copy } = useClipboard(); + const { trackUsage } = useTracking(); const { data, error } = useGetPreviewUrlQuery({ params: { contentType: model as UID.ContentType, @@ -43,11 +44,22 @@ const PreviewSidePanel: PanelComponent = ({ model, documentId, document }) => { }); }; + const handleClick = () => { + trackUsage('willOpenPreview'); + }; + return { title: formatMessage({ id: 'content-manager.preview.panel.title', defaultMessage: 'Preview' }), content: ( -