From 78a5cefb65cc91606166695f01dcc8ae663334d7 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 09:48:01 +0200 Subject: [PATCH 1/9] TASK: Bump minimum php version to 8.3 to match dependencies --- .github/workflows/tests.yml | 13 +++++++++---- composer.json | 2 +- phpstan.neon | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20f9e116e..6a5bc9961 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,13 +30,18 @@ jobs: codestyle: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['8.3'] + steps: - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: ${{ matrix.php-versions }} - name: Cache dependencies uses: actions/cache@v3 @@ -47,13 +52,13 @@ jobs: - name: Install dependencies uses: php-actions/composer@v6 with: - php_version: 8.1 + php_version: ${{ matrix.php-versions }} version: 2 - name: PHPStan uses: php-actions/phpstan@v3 with: - php_version: 8.1 + php_version: ${{ matrix.php-versions }} version: 2.1.17 command: analyse path: 'Classes/' @@ -68,7 +73,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.1'] + php-versions: ['8.3'] flow-versions: ['8.3'] steps: diff --git a/composer.json b/composer.json index cb1fb90a9..e68d53f79 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "This module allows managing media assets including pictures, videos, audio and documents.", "type": "neos-package", "require": { - "php": ">=8.1", + "php": ">=8.3", "neos/media": "^8.3", "neos/neos": "^8.3", "neos/neos-ui": "^8.3", diff --git a/phpstan.neon b/phpstan.neon index ca634f98c..710518656 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ parameters: level: 3 + phpVersion: 80300 paths: - Classes ignoreErrors: From e7d3ca749a22403cbd6b30738ae5b1d8acc38a31 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 10:55:59 +0200 Subject: [PATCH 2/9] TASK: Fix codestyle step in CI --- .github/workflows/tests.yml | 4 +--- Classes/GraphQL/Resolver.php | 3 ++- composer.json | 4 ++-- phpstan.neon | 2 ++ 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a5bc9961..5b43570d7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,9 +59,7 @@ jobs: uses: php-actions/phpstan@v3 with: php_version: ${{ matrix.php-versions }} - version: 2.1.17 - command: analyse - path: 'Classes/' + configuration: phpstan.neon php-unit-tests: env: diff --git a/Classes/GraphQL/Resolver.php b/Classes/GraphQL/Resolver.php index a25672849..cd8757b8b 100644 --- a/Classes/GraphQL/Resolver.php +++ b/Classes/GraphQL/Resolver.php @@ -29,6 +29,7 @@ use InvalidArgumentException; use UnitEnum; use Wwwision\Types\Exception\CoerceException; +use Wwwision\Types\Options; use Wwwision\Types\Parser; use Wwwision\Types\Schema\EnumCaseSchema; use Wwwision\Types\Schema\EnumSchema; @@ -94,7 +95,7 @@ public function typeConfigDecorator(array $typeConfig, TypeDefinitionNode $typeD $schema = Parser::getSchema($className); if ($schema instanceof EnumSchema) { $typeConfig['values'] = array_map(static fn(EnumCaseSchema $caseSchema - ) => $caseSchema->instantiate(null), $schema->caseSchemas); + ) => $caseSchema->instantiate(null, Options::create()), $schema->caseSchemas); } } return $typeConfig; diff --git a/composer.json b/composer.json index e68d53f79..5fc198a2a 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,10 @@ "wwwision/types-graphql": "^1.3" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^1.12 || ^2" }, "suggest": { - "phpstan/phpstan": "For running code quality checks", "flowpack/neos-asset-usage": "Allows filtering unused assets and other related features", "flowpack/entity-usage-databasestorage": "Required for the asset usage features" }, diff --git a/phpstan.neon b/phpstan.neon index 710518656..808e009d5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,8 @@ parameters: phpVersion: 80300 paths: - Classes + bootstrapFiles: + - vendor/autoload.php ignoreErrors: - '#Call to an undefined method [a-zA-Z0-9\\_]+Repository::findBy[a-zA-Z]+\(\)#' - '#Call to an undefined method [a-zA-Z0-9\\_]+Repository::findOneBy[a-zA-Z]+\(\)#' From 8ba17c3ce0db33e94f102b64a1c761b4347cbd81 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 11:03:58 +0200 Subject: [PATCH 3/9] TASK: Fix phpunit test --- Tests/Functional/GraphQL/AssetApiTest.php | 39 ++++---- Tests/Functional/TestAssetUsageStrategy.php | 98 +++++++++++++++++++++ 2 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 Tests/Functional/TestAssetUsageStrategy.php diff --git a/Tests/Functional/GraphQL/AssetApiTest.php b/Tests/Functional/GraphQL/AssetApiTest.php index 20fb4d513..937b1f659 100644 --- a/Tests/Functional/GraphQL/AssetApiTest.php +++ b/Tests/Functional/GraphQL/AssetApiTest.php @@ -18,9 +18,10 @@ use Flowpack\Media\Ui\GraphQL\Resolver\Type\AssetResolver; use Flowpack\Media\Ui\GraphQL\Types; use Flowpack\Media\Ui\Tests\Functional\AbstractMediaTestCase; +use Flowpack\Media\Ui\Tests\Functional\TestAssetUsageStrategy; use Neos\Flow\Persistence\Doctrine\PersistenceManager; use Neos\Flow\Tests\Behavior\Features\Bootstrap\SecurityOperationsTrait; -use Neos\Media\Domain\Service\AssetService; +use Neos\Media\Domain\Repository\AssetRepository; use function Wwwision\Types\instantiate; @@ -36,6 +37,11 @@ class AssetApiTest extends AbstractMediaTestCase */ protected static $testablePersistenceEnabled = true; + protected MediaApi $mediaApi; + protected AssetResolver $assetResolver; + protected TestAssetUsageStrategy $testAssetUsageStrategy; + protected AssetRepository $assetRepository; + public function setUp(): void { parent::setUp(); @@ -46,6 +52,11 @@ public function setUp(): void $this->mediaApi = $this->objectManager->get(MediaApi::class); $this->assetResolver = $this->objectManager->get(AssetResolver::class); + $this->testAssetUsageStrategy = $this->objectManager->get(TestAssetUsageStrategy::class); + $this->assetRepository = $this->objectManager->get(AssetRepository::class); + + // Reset the test strategy before each test + $this->testAssetUsageStrategy->reset(); $this->iAmAuthenticatedWithRole('Neos.Neos:Editor'); } @@ -83,8 +94,7 @@ public function testEditAsset(): void $this->persistenceManager->persistAll(); - $assets = $this->mediaApi->assets(); - $asset = $assets->getIterator()->current(); + $asset = $this->mediaApi->assets()->assets[0]; $this->assertEquals($file->clientFilename, $asset->filename->value); // Edit the asset @@ -98,7 +108,7 @@ public function testEditAsset(): void ); $this->assertTrue($editResult->success); - $editedAsset = $this->mediaApi->assets()->getIterator()->current(); + $editedAsset = $this->mediaApi->assets()->assets[0]; $this->assertEquals('new-name.svg', $editedAsset->filename->value); } @@ -107,11 +117,9 @@ public function testDeleteUnusedAssetWorks(): void $file = self::createFile(); $result = $this->mediaApi->uploadFiles(Types\UploadedFiles::fromArray([$file])); $this->assertCount(1, $result->values); - $this->persistenceManager->persistAll(); - $assets = $this->mediaApi->assets(); - $asset = $assets->getIterator()->current(); + $asset = $this->mediaApi->assets()->assets[0]; $this->assertEquals($file->clientFilename, $asset->filename->value); // Delete the asset @@ -125,20 +133,15 @@ public function testDeleteUnusedAssetWorks(): void public function testDeleteUsedAssetFails(): void { - $assetServiceMock = $this->getMockBuilder(AssetService::class)->setMethods( - ['isInUse'] - )->disableOriginalConstructor()->getMock(); - $assetServiceMock->expects(self::once())->method('isInUse')->willReturn(true); - $file = self::createFile(); - $result = $this->mediaApi->assets()->getIterator()->current(); - $this->assertEquals($file->clientFilename, $result->filename->value); - + $result = $this->mediaApi->uploadFiles(Types\UploadedFiles::fromArray([$file])); + $this->assertCount(1, $result->values); $this->persistenceManager->persistAll(); + $asset = $this->mediaApi->assets()->assets[0]; - $assets = $this->mediaApi->assets(); - $asset = $assets->getIterator()->current(); - $this->assertEquals($file->clientFilename, $asset->filename->value); + // Get the actual asset entity from repository and mark it as used + $assetEntity = $this->assetRepository->findByIdentifier($asset->id->value); + $this->testAssetUsageStrategy->markAssetAsUsed($assetEntity); // Try to delete the used asset $deleteResult = $this->mediaApi->deleteAsset($asset->id, $asset->assetSource->id); diff --git a/Tests/Functional/TestAssetUsageStrategy.php b/Tests/Functional/TestAssetUsageStrategy.php new file mode 100644 index 000000000..3c6d7dbd5 --- /dev/null +++ b/Tests/Functional/TestAssetUsageStrategy.php @@ -0,0 +1,98 @@ +testAssetUsageStrategy = $this->objectManager->get(TestAssetUsageStrategy::class); + * $this->assetRepository = $this->objectManager->get(AssetRepository::class); + * + * // In your test: + * $assetEntity = $this->assetRepository->findByIdentifier($assetId); + * $this->testAssetUsageStrategy->markAssetAsUsed($assetEntity); + * + * // Now AssetService->isInUse($assetEntity) will return true + * // and deletion attempts will fail + * ``` + * + * @Flow\Scope("singleton") + */ +class TestAssetUsageStrategy extends AbstractAssetUsageStrategy +{ + /** + * @var array + */ + protected $usedAssets = []; + + /** + * Mark an asset as being used + * + * @param AssetInterface $asset + * @return void + */ + public function markAssetAsUsed(AssetInterface $asset): void + { + $this->usedAssets[$asset->getIdentifier()] = $asset; + } + + /** + * Mark an asset as not being used + * + * @param AssetInterface $asset + * @return void + */ + public function markAssetAsUnused(AssetInterface $asset): void + { + unset($this->usedAssets[$asset->getIdentifier()]); + } + + /** + * Reset all usage markings + * + * @return void + */ + public function reset(): void + { + $this->usedAssets = []; + } + + /** + * Returns an array of usage reference objects. + * + * @param AssetInterface $asset + * @return array<\Neos\Media\Domain\Model\Dto\UsageReference> + */ + public function getUsageReferences(AssetInterface $asset): array + { + if (isset($this->usedAssets[$asset->getIdentifier()])) { + return [new UsageReference($asset)]; + } + + return []; + } +} From e92bae9706a8276ec7eb050e3303f5cf7ce6a6fc Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 11:06:28 +0200 Subject: [PATCH 4/9] TASK: Switch to chrome for testing --- .github/workflows/tests.yml | 2 -- package.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5b43570d7..886274ca6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -139,8 +139,6 @@ jobs: e2e: env: - # Solves error when parcel tries to count cpus via lscpu - PARCEL_WORKERS: 1 DEBUG: 'testcafe:tested-app:*' runs-on: ubuntu-latest diff --git a/package.json b/package.json index c5ae2586b..292239c34 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "build": "yarn build:module && yarn build:plugin", "build:module": "yarn workspace @media-ui/media-module run build", "build:plugin": "yarn workspace @media-ui/neos-ui-plugin run build", - "test:e2e:github-actions": "xvfb-run --server-args=\"-screen 0 1280x720x24\" yarn testcafe firefox" + "test:e2e:github-actions": "xvfb-run --server-args=\"-screen 0 1280x720x24\" yarn testcafe chrome" }, "workspaces": [ "packages/*" From f8389986b3afbeda562c1fdbddc721c914340882 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 12:11:27 +0200 Subject: [PATCH 5/9] TASK: Bump caniuse db --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c46f26bd1..66a3183e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4497,9 +4497,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001580, caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001718 - resolution: "caniuse-lite@npm:1.0.30001718" - checksum: 10c0/67f9ad09bc16443e28d14f265d6e468480cd8dc1900d0d8b982222de80c699c4f2306599c3da8a3fa7139f110d4b30d49dbac78f215470f479abb6ffe141d5d3 + version: 1.0.30001788 + resolution: "caniuse-lite@npm:1.0.30001788" + checksum: 10c0/d3c4695d0e7a1e95194cc5072e26db59cbcd25adfff64253859213c1a04ce9bc17f7b8ec8b11908ac1ecc6c1a0caf95fae0aec064a64b8df03286dffa629ce8a languageName: node linkType: hard From c479ad0e1d6f860dbdbd15f7ea852e490470085e Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 11:50:53 +0200 Subject: [PATCH 6/9] TASK: Fix e2e Github action --- .github/workflows/tests.yml | 2 +- .testcaferc.ci.json | 25 +++++++++++++++++++++++++ .testcaferc.json | 5 ++--- package.json | 3 +-- packages/media-module/tests/00_app.ts | 7 +------ 5 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 .testcaferc.ci.json diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 886274ca6..407eb74ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -160,7 +160,7 @@ jobs: run: yarn build:plugin - name: Run TestCafe - run: yarn test:e2e:github-actions + run: yarn testcafe --config-file .testcaferc.ci.json - name: Archive failure screenshots if: ${{ failure() }} diff --git a/.testcaferc.ci.json b/.testcaferc.ci.json new file mode 100644 index 000000000..1332066f8 --- /dev/null +++ b/.testcaferc.ci.json @@ -0,0 +1,25 @@ +{ + "browsers": "chrome:headless --window-size=1280,720 --no-sandbox --disable-dev-shm-usage --disable-gpu --disable-software-rasterizer --disable-extensions", + "src": ["packages/media-module/tests/**/*.ts"], + "appCommand": "yarn dev:test", + "appInitDelay": 3000, + "screenshots": { + "takeOnFails": true + }, + "reporter": [ + { + "name": "spec" + } + ], + "stopOnFirstFail": false, + "hostname": "localhost", + "baseUrl": "http://localhost:8000", + "pageLoadTimeout": 30000, + "pageRequestTimeout": 30000, + "ajaxRequestTimeout": 30000, + "browserInitTimeout": 60000, + "selectorTimeout": 10000, + "assertionTimeout": 10000, + "skipJsErrors": true, + "skipUncaughtErrors": true +} diff --git a/.testcaferc.json b/.testcaferc.json index b624c737a..6c1d18b8e 100644 --- a/.testcaferc.json +++ b/.testcaferc.json @@ -9,10 +9,9 @@ "screenshots": { "takeOnFails": true }, - "debugOnFail": false, "stopOnFirstFail": true, - "hostname": "127.0.0.1", - "baseUrl": "http://127.0.0.1:8000", + "hostname": "localhost", + "baseUrl": "http://localhost:8000", "retryTestPages": true, "pageLoadTimeout": 10000, "pageRequestTimeout": 60000, diff --git a/package.json b/package.json index 292239c34..828bd24dd 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,7 @@ "watch:plugin": "yarn workspace @media-ui/neos-ui-plugin run watch", "build": "yarn build:module && yarn build:plugin", "build:module": "yarn workspace @media-ui/media-module run build", - "build:plugin": "yarn workspace @media-ui/neos-ui-plugin run build", - "test:e2e:github-actions": "xvfb-run --server-args=\"-screen 0 1280x720x24\" yarn testcafe chrome" + "build:plugin": "yarn workspace @media-ui/neos-ui-plugin run build" }, "workspaces": [ "packages/*" diff --git a/packages/media-module/tests/00_app.ts b/packages/media-module/tests/00_app.ts index 332942e92..331d75cc1 100644 --- a/packages/media-module/tests/00_app.ts +++ b/packages/media-module/tests/00_app.ts @@ -1,15 +1,10 @@ -import { waitForReact } from 'testcafe-react-selectors'; import page from './page-model'; /** * This fixture should be run first to make sure the server is running and the app is loaded */ -fixture('App') - .page('./?reset=1') - .beforeEach(async () => { - await waitForReact(); - }); +fixture('App').page('./?reset=1'); test('The app loads and shows all main components', async (t) => { await t.expect(page.assetSourceList.exists).ok('The asset source list should exist'); From a8fba9d014ab728203926def12022e6d22773128 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 13:44:50 +0200 Subject: [PATCH 7/9] TASK: Switch unit tests to the node test runner --- .mocharc.json | 5 - package.json | 6 +- packages/core/src/domain/SearchTerm.spec.ts | 22 +- yarn.lock | 340 +++----------------- 4 files changed, 61 insertions(+), 312 deletions(-) delete mode 100644 .mocharc.json diff --git a/.mocharc.json b/.mocharc.json deleted file mode 100644 index 41051eece..000000000 --- a/.mocharc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extension": ["ts"], - "spec": "packages/**/*.spec.ts", - "require": "ts-node/register" -} diff --git a/package.json b/package.json index 828bd24dd..0090c6286 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "lint": "tsc --noemit && eslint 'packages/**/*.{js,jsx,ts,tsx}'", "test:e2e": "testcafe", - "test:unit": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' TS_NODE_FILES=true mocha", + "test:unit": "node --import tsx --test 'packages/**/*.spec.ts'", "dev": "yarn workspace @media-ui/dev-server run dev", "dev:test": "yarn workspace @media-ui/dev-server run test", "watch": "concurrently -k \"yarn watch:module\" \"yarn watch:plugin\"", @@ -28,10 +28,8 @@ "@media-ui/neos-ui-plugin": "workspace:*" }, "devDependencies": { - "@types/chai": "^4.3.5", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", - "chai": "^4.3.7", "concurrently": "^8.1.0", "esbuild": "~0.27.0", "eslint": "^8.42.0", @@ -40,12 +38,12 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testcafe": "^0.2.1", - "mocha": "^10.2.0", "prettier": "^2.8.8", "testcafe": "^3.7.2", "testcafe-react-selectors": "^5.0.3", "ts-loader": "^8.4.0", "ts-node": "^10.9.1", + "tsx": "^4.21.0", "typescript": "^4.9.5", "typescript-plugin-css-modules": "^5.0.1" }, diff --git a/packages/core/src/domain/SearchTerm.spec.ts b/packages/core/src/domain/SearchTerm.spec.ts index 2dbf5fb01..a1a7dde15 100644 --- a/packages/core/src/domain/SearchTerm.spec.ts +++ b/packages/core/src/domain/SearchTerm.spec.ts @@ -1,5 +1,5 @@ -import { describe, it } from 'mocha'; -import { expect } from 'chai'; +import { describe, it } from 'node:test'; +import assert from 'node:assert'; import { SearchTerm } from './SearchTerm'; @@ -8,8 +8,8 @@ describe('SearchTerm', () => { it('creates a SearchTerm', () => { const searchTerm = SearchTerm.fromString('Hello Search!'); - expect(searchTerm).to.be.instanceOf(SearchTerm); - expect(searchTerm.toString()).to.equal('Hello Search!'); + assert(searchTerm instanceof SearchTerm); + assert.strictEqual(searchTerm.toString(), 'Hello Search!'); }); }); @@ -18,16 +18,16 @@ describe('SearchTerm', () => { const url = new URL('https://neos.io/neos/management/mediaui?searchTerm=Test'); const searchTerm = SearchTerm.fromUrl(url); - expect(searchTerm).to.be.instanceOf(SearchTerm); - expect(searchTerm.toString()).to.equal('Test'); + assert(searchTerm instanceof SearchTerm); + assert.strictEqual(searchTerm.toString(), 'Test'); }); it('creates an empty SearchTerm if URL does not contain a searchTerm', () => { const url = new URL('https://neos.io/neos/management/mediaui'); const searchTerm = SearchTerm.fromUrl(url); - expect(searchTerm).to.be.instanceOf(SearchTerm); - expect(searchTerm.toString()).to.equal(''); + assert(searchTerm instanceof SearchTerm); + assert.strictEqual(searchTerm.toString(), ''); }); }); @@ -35,7 +35,7 @@ describe('SearchTerm', () => { it('provides the asset identifier included in the search term when it is present', () => { const searchTermWithAssetIdentifier = SearchTerm.fromString('id:68610fa2-bdd1-4d84-80eb-27db56f2889f'); - expect(searchTermWithAssetIdentifier.getAssetIdentifierIfPresent()).to.deep.equal({ + assert.deepStrictEqual(searchTermWithAssetIdentifier.getAssetIdentifierIfPresent(), { assetId: '68610fa2-bdd1-4d84-80eb-27db56f2889f', assetSourceId: '', }); @@ -44,7 +44,7 @@ describe('SearchTerm', () => { it("returns null when there's no asset identifier included in the search term", () => { const searchTermWithoutAssetIdentifier = SearchTerm.fromString('Hello Search!'); - expect(searchTermWithoutAssetIdentifier.getAssetIdentifierIfPresent()).to.be.null; + assert.strictEqual(searchTermWithoutAssetIdentifier.getAssetIdentifierIfPresent(), null); }); }); @@ -52,7 +52,7 @@ describe('SearchTerm', () => { it('properly converts the search term to a string', () => { const searchTermAsString = '' + SearchTerm.fromString('Hello Search!'); - expect(searchTermAsString).to.equal('Hello Search!'); + assert.strictEqual(searchTermAsString, 'Hello Search!'); }); }); }); diff --git a/yarn.lock b/yarn.lock index 66a3183e1..8eed9e24f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3106,13 +3106,6 @@ __metadata: languageName: node linkType: hard -"@types/chai@npm:^4.3.5": - version: 4.3.5 - resolution: "@types/chai@npm:4.3.5" - checksum: 10c0/816b3081e067c6e332be313e2e9a518b117c9eac51201cac749de0d6fcf7cb3238d0def37690b6539b3a928bd87b2f5f777914248447889ebc6f630a0d00e0e5 - languageName: node - linkType: hard - "@types/connect@npm:*": version: 3.4.34 resolution: "@types/connect@npm:3.4.34" @@ -3777,13 +3770,6 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.1": - version: 4.1.1 - resolution: "ansi-colors@npm:4.1.1" - checksum: 10c0/6086ade4336b4250b6b25e144b83e5623bcaf654d3df0c3546ce09c9c5ff999cb6a6f00c87e802d05cf98aef79d92dc76ade2670a2493b8dcb80220bec457838 - languageName: node - linkType: hard - "ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -4374,13 +4360,6 @@ __metadata: languageName: node linkType: hard -"browser-stdout@npm:1.3.1": - version: 1.3.1 - resolution: "browser-stdout@npm:1.3.1" - checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 - languageName: node - linkType: hard - "browserslist@npm:^4.22.2": version: 4.22.3 resolution: "browserslist@npm:4.22.3" @@ -4489,13 +4468,6 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.0.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - "caniuse-lite@npm:^1.0.30001580, caniuse-lite@npm:^1.0.30001669": version: 1.0.30001788 resolution: "caniuse-lite@npm:1.0.30001788" @@ -4517,21 +4489,6 @@ __metadata: languageName: node linkType: hard -"chai@npm:^4.3.7": - version: 4.3.7 - resolution: "chai@npm:4.3.7" - dependencies: - assertion-error: "npm:^1.1.0" - check-error: "npm:^1.0.2" - deep-eql: "npm:^4.1.2" - get-func-name: "npm:^2.0.0" - loupe: "npm:^2.3.1" - pathval: "npm:^1.1.1" - type-detect: "npm:^4.0.5" - checksum: 10c0/a11c6b74ce2d5587c3db1f1e5bf32073876319d4c65ba4e574ca9b56ec93ebbc80765e1fa4af354553afbf7ed245fb54c45d69d350a7b850c4aaf9f1e01f950f - languageName: node - linkType: hard - "chalk@npm:^2.3.0, chalk@npm:^2.4.0, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -4570,7 +4527,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3, chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.5.2": +"chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.5.2": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -4636,17 +4593,6 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^7.0.2": - version: 7.0.4 - resolution: "cliui@npm:7.0.4" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.0" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 - languageName: node - linkType: hard - "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -4966,7 +4912,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -5011,13 +4957,6 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^4.0.0": - version: 4.0.0 - resolution: "decamelize@npm:4.0.0" - checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d - languageName: node - linkType: hard - "decode-uri-component@npm:^0.2.0": version: 0.2.2 resolution: "decode-uri-component@npm:0.2.2" @@ -5055,15 +4994,6 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^4.1.2": - version: 4.1.2 - resolution: "deep-eql@npm:4.1.2" - dependencies: - type-detect: "npm:^4.0.0" - checksum: 10c0/621e31ba3ef923bdded8a84e04e01676dc3c6b8d1772fd150b80ae2476114d33cb8de2ae28ccad89a2f5aace6c596d0daed99c9681aadab7beb3e0ceec9dcc0e - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.3 resolution: "deep-is@npm:0.1.3" @@ -5203,13 +5133,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:5.0.0": - version: 5.0.0 - resolution: "diff@npm:5.0.0" - checksum: 10c0/08c5904779bbababcd31f1707657b1ad57f8a9b65e6f88d3fb501d09a965d5f8d73066898a7d3f35981f9e4101892c61d99175d421f3b759533213c253d91134 - languageName: node - linkType: hard - "diff@npm:^4.0.1, diff@npm:^4.0.2": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -5655,13 +5578,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:4.0.0, escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -5669,6 +5585,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + "escape-string-regexp@npm:^5.0.0": version: 5.0.0 resolution: "escape-string-regexp@npm:5.0.0" @@ -6168,16 +6091,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:5.0.0, find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a - languageName: node - linkType: hard - "find-up@npm:^3.0.0": version: 3.0.0 resolution: "find-up@npm:3.0.0" @@ -6187,6 +6100,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + "flat-cache@npm:^3.0.4": version: 3.0.4 resolution: "flat-cache@npm:3.0.4" @@ -6197,15 +6120,6 @@ __metadata: languageName: node linkType: hard -"flat@npm:^5.0.2": - version: 5.0.2 - resolution: "flat@npm:5.0.2" - bin: - flat: cli.js - checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe - languageName: node - linkType: hard - "flatted@npm:^3.1.0": version: 3.2.7 resolution: "flatted@npm:3.2.7" @@ -6481,20 +6395,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.2.0": - version: 7.2.0 - resolution: "glob@npm:7.2.0" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.0.4" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/478b40e38be5a3d514e64950e1e07e0ac120585add6a37c98d0ed24d72d9127d734d2a125786073c8deb687096e84ae82b641c441a869ada3a9cc91b68978632 - languageName: node - linkType: hard - "glob@npm:^7.0.3, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -6744,15 +6644,6 @@ __metadata: languageName: node linkType: hard -"he@npm:1.2.0": - version: 1.2.0 - resolution: "he@npm:1.2.0" - bin: - he: bin/he - checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 - languageName: node - linkType: hard - "highlight-es@npm:^1.0.0": version: 1.0.3 resolution: "highlight-es@npm:1.0.3" @@ -7293,13 +7184,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^2.1.0": - version: 2.1.0 - resolution: "is-plain-obj@npm:2.1.0" - checksum: 10c0/e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 - languageName: node - linkType: hard - "is-podman@npm:^1.0.1": version: 1.0.1 resolution: "is-podman@npm:1.0.1" @@ -7353,13 +7237,6 @@ __metadata: languageName: node linkType: hard -"is-unicode-supported@npm:^0.1.0": - version: 0.1.0 - resolution: "is-unicode-supported@npm:0.1.0" - checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 - languageName: node - linkType: hard - "is-utf8@npm:^0.2.0": version: 0.2.1 resolution: "is-utf8@npm:0.2.1" @@ -7425,7 +7302,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:4.1.0, js-yaml@npm:^4.1.0": +"js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -7818,16 +7695,6 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:4.1.0": - version: 4.1.0 - resolution: "log-symbols@npm:4.1.0" - dependencies: - chalk: "npm:^4.1.0" - is-unicode-supported: "npm:^0.1.0" - checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 - languageName: node - linkType: hard - "log-update-async-hook@npm:^2.0.7": version: 2.0.7 resolution: "log-update-async-hook@npm:2.0.7" @@ -7865,15 +7732,6 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^2.3.1": - version: 2.3.6 - resolution: "loupe@npm:2.3.6" - dependencies: - get-func-name: "npm:^2.0.0" - checksum: 10c0/a974841ce94ef2a35aac7144e7f9e789e3887f82286cd9ffe7ff00f2ac9d117481989948657465e2b0b102f23136d89ae0a18fd4a32d9015012cd64464453289 - languageName: node - linkType: hard - "lru-cache@npm:11.0.2": version: 11.0.2 resolution: "lru-cache@npm:11.0.2" @@ -8130,15 +7988,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:5.0.1": - version: 5.0.1 - resolution: "minimatch@npm:5.0.1" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/baa60fc5839205f13d6c266d8ad4d160ae37c33f66b130b5640acac66deff84b934ac6307f5dc5e4b30362c51284817c12df7c9746ffb600b9009c581e0b1634 - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -8261,38 +8110,6 @@ __metadata: languageName: node linkType: hard -"mocha@npm:^10.2.0": - version: 10.2.0 - resolution: "mocha@npm:10.2.0" - dependencies: - ansi-colors: "npm:4.1.1" - browser-stdout: "npm:1.3.1" - chokidar: "npm:3.5.3" - debug: "npm:4.3.4" - diff: "npm:5.0.0" - escape-string-regexp: "npm:4.0.0" - find-up: "npm:5.0.0" - glob: "npm:7.2.0" - he: "npm:1.2.0" - js-yaml: "npm:4.1.0" - log-symbols: "npm:4.1.0" - minimatch: "npm:5.0.1" - ms: "npm:2.1.3" - nanoid: "npm:3.3.3" - serialize-javascript: "npm:6.0.0" - strip-json-comments: "npm:3.1.1" - supports-color: "npm:8.1.1" - workerpool: "npm:6.2.1" - yargs: "npm:16.2.0" - yargs-parser: "npm:20.2.4" - yargs-unparser: "npm:2.0.0" - bin: - _mocha: bin/_mocha - mocha: bin/mocha.js - checksum: 10c0/145185435535ec7766932e9fa3def1520ffb895e7fa341e8592829844c4c9dd9fcb35db139d448c228b5ad3c2bedce50423ce760827b3054fafe6d47ef014937 - languageName: node - linkType: hard - "moment-duration-format-commonjs@npm:^1.0.0": version: 1.0.1 resolution: "moment-duration-format-commonjs@npm:1.0.1" @@ -8344,15 +8161,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.3": - version: 3.3.3 - resolution: "nanoid@npm:3.3.3" - bin: - nanoid: bin/nanoid.cjs - checksum: 10c0/d7ab68893cdb92dd2152d505e56571d571c65b71a9815f9dfb3c9a8cbf943fe43c9777d9a95a3b81ef01e442fec8409a84375c08f90a5753610a9f22672d953a - languageName: node - linkType: hard - "nanoid@npm:^3.1.12, nanoid@npm:^3.1.31": version: 3.3.4 resolution: "nanoid@npm:3.3.4" @@ -8419,10 +8227,8 @@ __metadata: "@media-ui/dev-server": "workspace:*" "@media-ui/media-module": "workspace:*" "@media-ui/neos-ui-plugin": "workspace:*" - "@types/chai": "npm:^4.3.5" "@typescript-eslint/eslint-plugin": "npm:^5.59.9" "@typescript-eslint/parser": "npm:^5.59.9" - chai: "npm:^4.3.7" concurrently: "npm:^8.1.0" esbuild: "npm:~0.27.0" eslint: "npm:^8.42.0" @@ -8431,12 +8237,12 @@ __metadata: eslint-plugin-react: "npm:^7.32.2" eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-testcafe: "npm:^0.2.1" - mocha: "npm:^10.2.0" prettier: "npm:^2.8.8" testcafe: "npm:^3.7.2" testcafe-react-selectors: "npm:^5.0.3" ts-loader: "npm:^8.4.0" ts-node: "npm:^10.9.1" + tsx: "npm:^4.21.0" typescript: "npm:^4.9.5" typescript-plugin-css-modules: "npm:^5.0.1" languageName: unknown @@ -9333,15 +9139,6 @@ __metadata: languageName: node linkType: hard -"randombytes@npm:^2.1.0": - version: 2.1.0 - resolution: "randombytes@npm:2.1.0" - dependencies: - safe-buffer: "npm:^5.1.0" - checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 - languageName: node - linkType: hard - "range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" @@ -9969,7 +9766,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -10144,15 +9941,6 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:6.0.0": - version: 6.0.0 - resolution: "serialize-javascript@npm:6.0.0" - dependencies: - randombytes: "npm:^2.1.0" - checksum: 10c0/73104922ef0a919064346eea21caab99de1a019a1f5fb54a7daa7fcabc39e83b387a2a363e52a889598c3b1bcf507c4b2a7b26df76e991a310657af20eea2e7c - languageName: node - linkType: hard - "serve-static@npm:1.14.1": version: 1.14.1 resolution: "serve-static@npm:1.14.1" @@ -10512,7 +10300,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:3.1.1, strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd @@ -10549,15 +10337,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:8.1.1, supports-color@npm:^8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -10576,6 +10355,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -11130,6 +10918,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.21.0": + version: 4.21.0 + resolution: "tsx@npm:4.21.0" + dependencies: + esbuild: "npm:~0.27.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b + languageName: node + linkType: hard + "tunnel-agent@npm:0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0" @@ -11608,13 +11412,6 @@ __metadata: languageName: node linkType: hard -"workerpool@npm:6.2.1": - version: 6.2.1 - resolution: "workerpool@npm:6.2.1" - checksum: 10c0/f0efd2d74eafd58eaeb36d7d85837d080f75c52b64893cff317b66257dd308e5c9f85ef0b12904f6c7f24ed2365bc3cfeba1f1d16aa736d84d6ef8156ae37c80 - languageName: node - linkType: hard - "wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -11688,20 +11485,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:20.2.4": - version: 20.2.4 - resolution: "yargs-parser@npm:20.2.4" - checksum: 10c0/08dc341f0b9f940c2fffc1d1decf3be00e28cabd2b578a694901eccc7dcd10577f10c6aa1b040fdd9a68b2042515a60f18476543bccacf9f3ce2c8534cd87435 - languageName: node - linkType: hard - -"yargs-parser@npm:^20.2.2": - version: 20.2.7 - resolution: "yargs-parser@npm:20.2.7" - checksum: 10c0/0c64f63502899661328b8bc95519d33355d03e56c42ba6ed28cbe36912564769eec3f2123f40529cd725acf8a3ed8ec96c4ecf45ff3b81a4cb3ad1be937afbc8 - languageName: node - linkType: hard - "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -11709,33 +11492,6 @@ __metadata: languageName: node linkType: hard -"yargs-unparser@npm:2.0.0": - version: 2.0.0 - resolution: "yargs-unparser@npm:2.0.0" - dependencies: - camelcase: "npm:^6.0.0" - decamelize: "npm:^4.0.0" - flat: "npm:^5.0.2" - is-plain-obj: "npm:^2.1.0" - checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 - languageName: node - linkType: hard - -"yargs@npm:16.2.0": - version: 16.2.0 - resolution: "yargs@npm:16.2.0" - dependencies: - cliui: "npm:^7.0.2" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.0" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^20.2.2" - checksum: 10c0/b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 - languageName: node - linkType: hard - "yargs@npm:^17.7.1": version: 17.7.1 resolution: "yargs@npm:17.7.1" From cbbb68908d52a09025274495897675ffa3a5d056 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 13:55:05 +0200 Subject: [PATCH 8/9] TASK: Update testcafe --- package.json | 2 +- yarn.lock | 50 ++++++++++++++++++++++++-------------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 0090c6286..e7974e905 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testcafe": "^0.2.1", "prettier": "^2.8.8", - "testcafe": "^3.7.2", + "testcafe": "^3.7.4", "testcafe-react-selectors": "^5.0.3", "ts-loader": "^8.4.0", "ts-node": "^10.9.1", diff --git a/yarn.lock b/yarn.lock index 8eed9e24f..ae8e8f94a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5133,13 +5133,20 @@ __metadata: languageName: node linkType: hard -"diff@npm:^4.0.1, diff@npm:^4.0.2": +"diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 languageName: node linkType: hard +"diff@npm:^8.0.3": + version: 8.0.4 + resolution: "diff@npm:8.0.4" + checksum: 10c0/7ee5d03926db4039be7252ac3b0abaae1bd122a2ca971e5ca7270e444e36ff83dd906fad1a719740ca347e97ed5dc8f458a76a8391dbcd7aff363bdafb348a00 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -8238,7 +8245,7 @@ __metadata: eslint-plugin-react-hooks: "npm:^4.6.0" eslint-plugin-testcafe: "npm:^0.2.1" prettier: "npm:^2.8.8" - testcafe: "npm:^3.7.2" + testcafe: "npm:^3.7.4" testcafe-react-selectors: "npm:^5.0.3" ts-loader: "npm:^8.4.0" ts-node: "npm:^10.9.1" @@ -8543,13 +8550,6 @@ __metadata: languageName: node linkType: hard -"os-tmpdir@npm:~1.0.1": - version: 1.0.2 - resolution: "os-tmpdir@npm:1.0.2" - checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 - languageName: node - linkType: hard - "p-finally@npm:^2.0.0": version: 2.0.1 resolution: "p-finally@npm:2.0.1" @@ -10431,9 +10431,9 @@ __metadata: languageName: node linkType: hard -"testcafe-hammerhead@npm:31.7.5": - version: 31.7.5 - resolution: "testcafe-hammerhead@npm:31.7.5" +"testcafe-hammerhead@npm:31.7.7": + version: 31.7.7 + resolution: "testcafe-hammerhead@npm:31.7.7" dependencies: "@adobe/css-tools": "npm:^4.3.0-rc.1" "@electron/asar": "npm:^3.2.3" @@ -10460,7 +10460,7 @@ __metadata: tough-cookie: "npm:4.1.3" tunnel-agent: "npm:0.6.0" ws: "npm:^7.4.6" - checksum: 10c0/9a2ecedcb7cccbc74833b2baf9c7fd46844427ebd2948fd28bc1a07c7d451166b00d172fbb91bcbe9d94f5072a812ea2afed3648075048aea4baad2078028a1a + checksum: 10c0/b409b21fae5277487e8779b904dcbd0c1080322099693e91652b608e9c0e9d1c5062bd7a3bcdadcbe657dce4a38da411d9df6a703454aef58411ac18f270124e languageName: node linkType: hard @@ -10568,9 +10568,9 @@ __metadata: languageName: node linkType: hard -"testcafe@npm:^3.7.2": - version: 3.7.2 - resolution: "testcafe@npm:3.7.2" +"testcafe@npm:^3.7.4": + version: 3.7.4 + resolution: "testcafe@npm:3.7.4" dependencies: "@babel/core": "npm:^7.23.2" "@babel/plugin-proposal-decorators": "npm:^7.23.2" @@ -10608,7 +10608,7 @@ __metadata: del: "npm:^3.0.0" device-specs: "npm:^1.0.0" devtools-protocol: "npm:0.0.1109433" - diff: "npm:^4.0.2" + diff: "npm:^8.0.3" elegant-spinner: "npm:^1.0.1" email-validator: "npm:^2.0.4" emittery: "npm:^0.4.1" @@ -10655,7 +10655,7 @@ __metadata: source-map-support: "npm:^0.5.16" strip-bom: "npm:^2.0.0" testcafe-browser-tools: "npm:2.0.26" - testcafe-hammerhead: "npm:31.7.5" + testcafe-hammerhead: "npm:31.7.7" testcafe-legacy-api: "npm:5.1.8" testcafe-reporter-json: "npm:^2.1.0" testcafe-reporter-list: "npm:^2.2.0" @@ -10664,14 +10664,14 @@ __metadata: testcafe-reporter-xunit: "npm:^2.2.1" testcafe-selector-generator: "npm:^0.1.0" time-limit-promise: "npm:^1.0.2" - tmp: "npm:0.0.28" + tmp: "npm:0.2.5" tree-kill: "npm:^1.2.2" typescript: "npm:4.7.4" unquote: "npm:^1.1.1" url-to-options: "npm:^2.0.0" bin: testcafe: bin/testcafe-with-v8-flag-filter.js - checksum: 10c0/dd0128b2419dcd4697011ac66429ebf9762e2fff00b0307772f9e383fa32898245a55a4af658f9a1a0aecc8f18abdcd32c74641823b96ddffcb4cf317f5c6184 + checksum: 10c0/0b8e96777a227b2a23cd1891a960872d3ea380ae645fdfdd23eb263edb257171c5d8d3ba4772c1c9575dd3c7621d2dcfb0f515b875c15bff59957ce20f0e5bd2 languageName: node linkType: hard @@ -10689,12 +10689,10 @@ __metadata: languageName: node linkType: hard -"tmp@npm:0.0.28": - version: 0.0.28 - resolution: "tmp@npm:0.0.28" - dependencies: - os-tmpdir: "npm:~1.0.1" - checksum: 10c0/2be352b43206411d4f7c29d09afa03c94e93386fdd87d9973aecd2b6569b8fda1e7a96ed613ac9b2dc30038a9a3ec762b9f4b910d311e565124653750a991dac +"tmp@npm:0.2.5": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10c0/cee5bb7d674bb4ba3ab3f3841c2ca7e46daeb2109eec395c1ec7329a91d52fcb21032b79ac25161a37b2565c4858fefab927af9735926a113ef7bac9091a6e0e languageName: node linkType: hard From 0c410f49a03cd572269f5140a84254f1e728b8f1 Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 14 Apr 2026 21:55:27 +0200 Subject: [PATCH 9/9] TASK: Replace react selectors with css selectors for e2e tests --- .testcaferc.ci.json | 6 ++- .testcaferc.json | 6 ++- package.json | 1 - .../components/AssetCollectionTreeNode.tsx | 2 +- .../src/components/TagTreeNode.tsx | 2 +- .../src/components/CreateTagDialog.tsx | 4 +- packages/core/src/components/Dialog.tsx | 1 + packages/dev-server/src/server.ts | 8 +-- packages/media-module/tests/clipboard.ts | 16 +++--- packages/media-module/tests/inspector.ts | 4 +- packages/media-module/tests/lightbox.ts | 7 +-- packages/media-module/tests/page-model.ts | 51 ++++++++++--------- packages/media-module/tests/tags.ts | 28 +++++----- yarn.lock | 10 ---- 14 files changed, 69 insertions(+), 77 deletions(-) diff --git a/.testcaferc.ci.json b/.testcaferc.ci.json index 1332066f8..c5a69a4b7 100644 --- a/.testcaferc.ci.json +++ b/.testcaferc.ci.json @@ -1,6 +1,8 @@ { - "browsers": "chrome:headless --window-size=1280,720 --no-sandbox --disable-dev-shm-usage --disable-gpu --disable-software-rasterizer --disable-extensions", - "src": ["packages/media-module/tests/**/*.ts"], + "browsers": "chrome:headless --window-size=1280,720 --incognito --no-sandbox --disable-dev-shm-usage --disable-gpu --disable-software-rasterizer --disable-extensions", + "src": [ + "packages/media-module/tests/**/*.ts" + ], "appCommand": "yarn dev:test", "appInitDelay": 3000, "screenshots": { diff --git a/.testcaferc.json b/.testcaferc.json index 6c1d18b8e..6a7cebb69 100644 --- a/.testcaferc.json +++ b/.testcaferc.json @@ -1,9 +1,11 @@ { "browsers": { "path": "chromium", - "cmd": "--window-size=1280,720" + "cmd": "--window-size=1280,720 --incognito" }, - "src": ["packages/media-module/tests/**/*.ts"], + "src": [ + "packages/media-module/tests/**/*.ts" + ], "appCommand": "yarn dev:test", "appInitDelay": 2000, "screenshots": { diff --git a/package.json b/package.json index e7974e905..62a57837f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "eslint-plugin-testcafe": "^0.2.1", "prettier": "^2.8.8", "testcafe": "^3.7.4", - "testcafe-react-selectors": "^5.0.3", "ts-loader": "^8.4.0", "ts-node": "^10.9.1", "tsx": "^4.21.0", diff --git a/packages/asset-collections/src/components/AssetCollectionTreeNode.tsx b/packages/asset-collections/src/components/AssetCollectionTreeNode.tsx index 82a9d9341..fe0eeea80 100644 --- a/packages/asset-collections/src/components/AssetCollectionTreeNode.tsx +++ b/packages/asset-collections/src/components/AssetCollectionTreeNode.tsx @@ -88,7 +88,7 @@ const AssetCollectionTreeNode: React.FC = ({ !config.canManageAssetCollections || !assetCollectionId || assetCollectionId === UNASSIGNED_COLLECTION_ID; return ( - + = ({ const isFocused = useRecoilValue(tagFocusedState({ assetCollectionId, tagId })); return ( - + { return ( { ]} >
- + void; diff --git a/packages/dev-server/src/server.ts b/packages/dev-server/src/server.ts index 4e7170a70..9f367a8f3 100644 --- a/packages/dev-server/src/server.ts +++ b/packages/dev-server/src/server.ts @@ -311,11 +311,11 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); return false; }, createTag: ($_, { tag: newTag }: { tag: Tag }): Tag => { - if (!tags.find((tag) => tag === newTag)) { - tags.push(newTag); - return newTag; + if (tags.find((tag) => tag === newTag)) { + throw new Error('Tag with this id already exists'); } - return null; + tags.push(newTag); + return newTag; }, updateTag: ($_, { id, label }): Tag => { throw new Error('Not implemented'); diff --git a/packages/media-module/tests/clipboard.ts b/packages/media-module/tests/clipboard.ts index 80af46713..5589c95f6 100644 --- a/packages/media-module/tests/clipboard.ts +++ b/packages/media-module/tests/clipboard.ts @@ -1,5 +1,5 @@ +import { Selector } from 'testcafe'; import page from './page-model'; -import { ReactSelector } from 'testcafe-react-selectors'; fixture('Clipboard').page('./?reset=1'); @@ -13,7 +13,7 @@ test('Asset can be added and removed from the clipboard', async (t) => { .ok('The first asset was added to the clipboard'); await t - .click(page.clipboardToggle.findReact('Button').withText('Clipboard (1)')) + .click(page.clipboardToggle.find('button').withText('Clipboard (1)')) .expect(page.thumbnails.count) .eql(1, 'The clipboard is visible and contains one asset'); @@ -35,19 +35,19 @@ test('The clipboard can be flushed', async (t) => { .ok('Two assets were added to the clipboard'); await t - .click(page.clipboardToggle.findReact('Button').withText('Clipboard (2)')) - .click(ReactSelector('ClipboardActions').find('[title="Flush clipboard"]')) - .click(ReactSelector('Dialog').findReact('Button').withText('Cancel')) + .click(page.clipboardToggle.find('button').withText('Clipboard (2)')) + .click(Selector('div[class*="clipboardActions"]').find('[title="Flush clipboard"]')) + .click(Selector('div[class*="dialog"]').find('button').withText('Cancel')) .expect(page.thumbnails.count) .eql(2, 'The clipboard was not flushed and the assets are still present'); await t - .click(ReactSelector('ClipboardActions').find('[title="Flush clipboard"]')) - .click(ReactSelector('Dialog').findReact('Button').withText('Yes, proceed with flushing the clipboard')) + .click(Selector('div[class*="clipboardActions"]').find('[title="Flush clipboard"]')) + .click(Selector('div[class*="dialog"]').find('button').withText('Yes, proceed with flushing the clipboard')) .expect(page.clipboardToggle.withText('Clipboard (0)').exists) .ok('The clipboard was flushed and the toggle shows 0 items') .expect(page.thumbnails.count) .eql(0, 'The clipboard is empty the counter is 0') - .expect(ReactSelector('Button').withText('Close clipboard').exists) + .expect(Selector('button').withText('Close clipboard').exists) .ok('A button to close the clipboard is available after flushing it'); }); diff --git a/packages/media-module/tests/inspector.ts b/packages/media-module/tests/inspector.ts index eb9dd7037..afdd0e31a 100644 --- a/packages/media-module/tests/inspector.ts +++ b/packages/media-module/tests/inspector.ts @@ -1,4 +1,4 @@ -import { ReactSelector } from 'testcafe-react-selectors'; +import { Selector } from 'testcafe'; import page from './page-model'; @@ -16,7 +16,7 @@ test('Tagging works', async (t) => { .click(page.firstThumbnail) .scrollIntoView(page.tagSelection) .click(page.tagSelection) - .click(ReactSelector('ListPreviewElement').withText('Example tag 1')); + .click(Selector('div[class*="listPreviewElement"]').withText('Example tag 1')); }).after(async (t) => { const { log } = await t.getBrowserConsoleMessages(); await t diff --git a/packages/media-module/tests/lightbox.ts b/packages/media-module/tests/lightbox.ts index 4972e8313..8d54aa089 100644 --- a/packages/media-module/tests/lightbox.ts +++ b/packages/media-module/tests/lightbox.ts @@ -5,12 +5,7 @@ fixture('Lightbox').page('./?reset=1'); test('Preview opens lightbox', async (t) => { await t .hover(page.firstThumbnail) - .click( - page.firstThumbnail - .findReact('AssetActions') - .findReact('IconButton') - .withAttribute('title', 'Preview asset') - ) + .click(page.firstThumbnail.find('.Thumbnail_toolBar').find('button[title="Preview asset"]')) .expect(page.lightbox.find('.ril-image-current').getAttribute('src')) .eql(await page.firstThumbnail.find('picture img').getAttribute('src')); }); diff --git a/packages/media-module/tests/page-model.ts b/packages/media-module/tests/page-model.ts index 3707889e1..d5eb7c40f 100644 --- a/packages/media-module/tests/page-model.ts +++ b/packages/media-module/tests/page-model.ts @@ -1,8 +1,8 @@ import { Selector } from 'testcafe'; -import { ReactSelector } from 'testcafe-react-selectors'; class Page { public collectionTree: Selector; + public collectionTreeToolbar: Selector; public thumbnails: Selector; public assetCollections: Selector; public pagination: Selector; @@ -19,36 +19,39 @@ class Page { public clipboardToggle: Selector; constructor() { + // Asset sources + this.assetSourceList = Selector('.AssetSourceList_assetSourceList'); + // Collection tree - this.collectionTree = ReactSelector('AssetCollectionTree'); - this.assetCollections = this.collectionTree.findReact('AssetCollectionTreeNode'); - this.tags = this.assetCollections.withText('All').findReact('TagTreeNode'); + this.collectionTree = Selector('.AssetCollectionTree_assetCollectionTree'); + this.collectionTreeToolbar = Selector('.AssetCollectionTree_toolbar'); + this.assetCollections = this.collectionTree.find('.AssetCollectionTreeNode'); - // Asset sources - this.assetSourceList = ReactSelector('AssetSourceList'); + // Tags are nested elements under the "All" collection + this.tags = this.assetCollections.withText('All').parent().find('.TagTreeNode'); // Main area - this.thumbnails = ReactSelector('Thumbnail'); - this.lightbox = ReactSelector('ReactImageLightbox'); + this.thumbnails = Selector('.Thumbnail_thumbnail'); + this.lightbox = Selector('.ril-outer'); // ReactImageLightbox uses this class // Bottom bar - this.assetCount = ReactSelector('AssetCount'); - this.clipboardToggle = ReactSelector('ClipboardToggle'); + this.assetCount = Selector('.AssetCount_assetCount'); + this.clipboardToggle = Selector('.ClipboardToggle_clipboardToggle'); // Pagination - this.pagination = ReactSelector('Pagination'); + this.pagination = Selector('nav[class*="pagination"]'); // Sorting - this.sortOrder = ReactSelector('SortOrderSelector'); + this.sortOrder = Selector('.SortOrderSelector_sortingState'); // Filtering - this.assetsFilter = ReactSelector('AssetsFilter'); + this.assetsFilter = Selector('.AssetsFilter_assetsFilter'); // Right sidebar - this.assetInspector = ReactSelector('AssetInspector'); - this.currentSelection = ReactSelector('CurrentSelection'); - this.tagSelection = this.assetInspector.findReact('TagSelectBox'); - this.inspectorActions = this.assetInspector.findReact('Actions'); + this.assetInspector = Selector('.InspectorContainer_inspector'); + this.currentSelection = Selector('div[class*="currentSelection"]'); + this.tagSelection = this.assetInspector.find('.TagSelectBox_tagSelectBox'); + this.inspectorActions = this.assetInspector.find('.Actions_actions'); } public get firstThumbnail() { @@ -60,30 +63,32 @@ class Page { } public get paginationItems() { - // FIXME: The ReactSelector does not work in the CI build, so we use a CSS selector as workaround for now return this.pagination.find('li'); } public get sortingSelection() { - return this.sortOrder.findReact('SelectBox'); + return this.sortOrder.find('div[class*="selectBox"]'); } public get sortingButton() { - return this.sortOrder.findReact('IconButton'); + return this.sortOrder.find('button'); } public get assetTypeFilter() { - return this.assetsFilter.findReact('AssetTypeFilter'); + return this.assetsFilter.find('.AssetsFilter_typeFilter'); } public get mediaTypeFilter() { - return this.assetsFilter.findReact('MediaTypeFilter'); + return this.assetsFilter.find('div[class*="mediaTypeFilter"]'); } - // FIXME: This is a workaround for issues with selecting elements inside a portal which is used by the UI react components for dropdowns public getDropdownElement(label: string): Selector { return Selector('ul[class*="dropDown__contents"]').find('[role="button"]').withText(label); } + + public get createTagDialog(): Selector { + return Selector('#CreateTagDialog'); + } } export default new Page(); diff --git a/packages/media-module/tests/tags.ts b/packages/media-module/tests/tags.ts index 702a688bf..55b5b01d0 100644 --- a/packages/media-module/tests/tags.ts +++ b/packages/media-module/tests/tags.ts @@ -1,5 +1,5 @@ +import { Selector } from 'testcafe'; import page from './page-model'; -import { ReactSelector } from 'testcafe-react-selectors'; fixture('Tags').page('./?reset=1'); @@ -26,29 +26,25 @@ test('Create a new tag and test validation', async (t) => { subSection('Check existing tag label validation'); await t .click(page.assetCollections.withText('All')) - .click(page.collectionTree.findReact('AddTagButton')) - .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), 'Example tag 1') - .expect( - ReactSelector('CreateTagDialog') - .findReact('TextInput') - .withProps({ validationerrors: ['This input is invalid'] }).exists - ) + .click(page.collectionTreeToolbar.find('button').withAttribute('title', 'Create new tag')) + .typeText(page.createTagDialog.find('input[class*="textInput"]'), 'Example tag 1') + .expect(page.createTagDialog.find('input').withAttribute('validationerrors').exists) .ok('Text input should have validation errors') - .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists) + .expect(page.createTagDialog.find('button').withText('Create').hasAttribute('disabled')) .ok('Create button should be disabled') - .expect(ReactSelector('CreateTagDialog').find('ul li').textContent) + .expect(page.createTagDialog.find('ul li').textContent) .eql('A tag with this label already exists') - .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), '00') - .expect(ReactSelector('CreateTagDialog').find('ul li').exists) + .typeText(page.createTagDialog.find('input[class*="textInput"]'), '00') + .expect(page.createTagDialog.find('ul li').exists) .notOk('The tooltip should not be visible anymore') - .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: false }).exists) + .expect(page.createTagDialog.find('button:not([disabled])').exists) .ok('Create button should be enabled'); subSection('Check empty tag label validation'); await t - .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), ' ', { replace: true }) - .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists) + .typeText(page.createTagDialog.find('input[class*="textInput"]'), ' ', { replace: true }) + .expect(page.createTagDialog.find('button[disabled]').exists) .ok('Create button should be disabled') - .expect(ReactSelector('CreateTagDialog').find('ul li').textContent) + .expect(page.createTagDialog.find('ul li').textContent) .eql('Please provide a tag label'); }); diff --git a/yarn.lock b/yarn.lock index ae8e8f94a..106a3a53c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8246,7 +8246,6 @@ __metadata: eslint-plugin-testcafe: "npm:^0.2.1" prettier: "npm:^2.8.8" testcafe: "npm:^3.7.4" - testcafe-react-selectors: "npm:^5.0.3" ts-loader: "npm:^8.4.0" ts-node: "npm:^10.9.1" tsx: "npm:^4.21.0" @@ -10517,15 +10516,6 @@ __metadata: languageName: node linkType: hard -"testcafe-react-selectors@npm:^5.0.3": - version: 5.0.3 - resolution: "testcafe-react-selectors@npm:5.0.3" - peerDependencies: - testcafe: ">1.0.0" - checksum: 10c0/46039f9468879412b66e864a746ae74583179260a3ae965a63554d5487289b6582de0b6f2feb58fa3047e8011fea665c937c1a9ed57c3930348aa0156a5ec560 - languageName: node - linkType: hard - "testcafe-reporter-json@npm:^2.1.0": version: 2.2.0 resolution: "testcafe-reporter-json@npm:2.2.0"