From c1678a15546a8729baa0fc1f3d8e6f81c1d1d79e Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 30 Mar 2026 18:52:00 +0200 Subject: [PATCH 1/6] feat: support `shadow-dom-testing-library` --- src/utils/index.ts | 36 ++++--- tests/rules/await-async-queries.test.ts | 6 ++ tests/rules/await-async-utils.test.ts | 1 + tests/rules/no-container.test.ts | 1 + tests/rules/no-debugging-utils.test.ts | 1 + tests/rules/no-node-access.test.ts | 1 + tests/rules/no-render-in-lifecycle.test.ts | 1 + tests/rules/no-test-id-queries.test.ts | 100 +++++++++++------- tests/rules/prefer-find-by.test.ts | 1 + .../prefer-query-by-disappearance.test.ts | 1 + tests/rules/prefer-screen-queries.test.ts | 1 + tests/utils/is-testing-library-module.test.ts | 3 +- 12 files changed, 99 insertions(+), 54 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 065a15b9..53966db7 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -28,6 +28,7 @@ const LIBRARY_MODULES = [ '@testing-library/vue', '@testing-library/svelte', '@marko/testing-library', + 'shadow-dom-testing-library', ] as const; const USER_EVENT_MODULE = '@testing-library/user-event'; @@ -59,6 +60,15 @@ const ALL_QUERIES_METHODS = [ 'ByDisplayValue', 'ByRole', 'ByTestId', + // queries coming from 'shadow-dom-testing-library' + 'ByShadowLabelText', + 'ByShadowPlaceholderText', + 'ByShadowText', + 'ByShadowAltText', + 'ByShadowTitle', + 'ByShadowDisplayValue', + 'ByShadowRole', + 'ByShadowTestId', ] as const; const SYNC_QUERIES_COMBINATIONS = combineQueries( @@ -157,27 +167,27 @@ const PRESENCE_MATCHERS = [ const ABSENCE_MATCHERS = ['toBeNull', 'toBeFalsy'] as const; export { - combineQueries, - getDocsUrl, - SYNC_QUERIES_VARIANTS, - ASYNC_QUERIES_VARIANTS, - ALL_QUERIES_VARIANTS, + ABSENCE_MATCHERS, + ALL_QUERIES_COMBINATIONS, ALL_QUERIES_METHODS, - SYNC_QUERIES_COMBINATIONS, + ALL_QUERIES_VARIANTS, + ALL_RETURNING_NODES, ASYNC_QUERIES_COMBINATIONS, - ALL_QUERIES_COMBINATIONS, + ASYNC_QUERIES_VARIANTS, ASYNC_UTILS, + combineQueries, DEBUG_UTILS, + EVENT_HANDLER_METHODS, EVENTS_SIMULATORS, - TESTING_FRAMEWORK_SETUP_HOOKS, + getDocsUrl, LIBRARY_MODULES, - PROPERTIES_RETURNING_NODES, METHODS_RETURNING_NODES, - ALL_RETURNING_NODES, + OLD_LIBRARY_MODULES, PRESENCE_MATCHERS, - ABSENCE_MATCHERS, - EVENT_HANDLER_METHODS, + PROPERTIES_RETURNING_NODES, + SYNC_QUERIES_COMBINATIONS, + SYNC_QUERIES_VARIANTS, + TESTING_FRAMEWORK_SETUP_HOOKS, USER_EVENT_METHODS, USER_EVENT_MODULE, - OLD_LIBRARY_MODULES, }; diff --git a/tests/rules/await-async-queries.test.ts b/tests/rules/await-async-queries.test.ts index 932a5ac2..1485c058 100644 --- a/tests/rules/await-async-queries.test.ts +++ b/tests/rules/await-async-queries.test.ts @@ -25,6 +25,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; interface TestCode { @@ -121,6 +122,11 @@ ruleTester.run(rule.name, rule, { testingFramework: '@marko/testing-library', }), + // async shadow-dom-testing-library screen queries declaration are valid + ...createTestCase((query) => `await screen.${query}('foo')`, { + testingFramework: 'shadow-dom-testing-library', + }), + // async queries not called are valid ...createTestCase((query) => `expect(screen.${query}).toBeDefined()`, { isAsync: false, diff --git a/tests/rules/await-async-utils.test.ts b/tests/rules/await-async-utils.test.ts index 2713e99f..89cd9ed9 100644 --- a/tests/rules/await-async-utils.test.ts +++ b/tests/rules/await-async-utils.test.ts @@ -19,6 +19,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; ruleTester.run(rule.name, rule, { diff --git a/tests/rules/no-container.test.ts b/tests/rules/no-container.test.ts index e4fcec8b..4b6db2d4 100644 --- a/tests/rules/no-container.test.ts +++ b/tests/rules/no-container.test.ts @@ -8,6 +8,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; ruleTester.run(rule.name, rule, { diff --git a/tests/rules/no-debugging-utils.test.ts b/tests/rules/no-debugging-utils.test.ts index db025f82..067ae14a 100644 --- a/tests/rules/no-debugging-utils.test.ts +++ b/tests/rules/no-debugging-utils.test.ts @@ -8,6 +8,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; ruleTester.run(rule.name, rule, { diff --git a/tests/rules/no-node-access.test.ts b/tests/rules/no-node-access.test.ts index 2c82251e..fb332957 100644 --- a/tests/rules/no-node-access.test.ts +++ b/tests/rules/no-node-access.test.ts @@ -18,6 +18,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; ruleTester.run(rule.name, rule, { diff --git a/tests/rules/no-render-in-lifecycle.test.ts b/tests/rules/no-render-in-lifecycle.test.ts index f47dc771..f8bee1ef 100644 --- a/tests/rules/no-render-in-lifecycle.test.ts +++ b/tests/rules/no-render-in-lifecycle.test.ts @@ -9,6 +9,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; ruleTester.run(rule.name, rule, { diff --git a/tests/rules/no-test-id-queries.test.ts b/tests/rules/no-test-id-queries.test.ts index 5dddc56a..f1e75555 100644 --- a/tests/rules/no-test-id-queries.test.ts +++ b/tests/rules/no-test-id-queries.test.ts @@ -9,6 +9,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; const QUERIES = [ @@ -20,6 +21,47 @@ const QUERIES = [ 'findAllByTestId', ]; +const createInvalidTestCase = (framework: string, query: string) => { + return [ + { + code: ` + import { render } from '${framework}'; + + test('test', async () => { + const { ${query} } = render(); + + expect(${query}('my-test-id')).toBeInTheDocument(); + }); + `, + errors: [ + { + messageId: 'noTestIdQueries', + line: 7, + column: 14, + }, + ], + }, + { + code: ` + import { render, screen } from '${framework}'; + + test('test', async () => { + render(); + + expect(screen.${query}('my-test-id')).toBeInTheDocument(); + }); + `, + errors: [ + { + messageId: 'noTestIdQueries', + line: 7, + column: 14, + }, + ], + }, + ] as const; +}; + ruleTester.run(rule.name, rule, { valid: [ ` @@ -43,44 +85,22 @@ ruleTester.run(rule.name, rule, { `, ], - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((framework) => - QUERIES.flatMap((query) => [ - { - code: ` - import { render } from '${framework}'; - - test('test', async () => { - const { ${query} } = render(); - - expect(${query}('my-test-id')).toBeInTheDocument(); - }); - `, - errors: [ - { - messageId: 'noTestIdQueries', - line: 7, - column: 14, - }, - ], - }, - { - code: ` - import { render, screen } from '${framework}'; - - test('test', async () => { - render(); - - expect(screen.${query}('my-test-id')).toBeInTheDocument(); - }); - `, - errors: [ - { - messageId: 'noTestIdQueries', - line: 7, - column: 14, - }, - ], - }, - ]) - ), + invalid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.flatMap((framework) => + QUERIES.flatMap((query) => createInvalidTestCase(framework, query)) + ), + // special cases for shadow-dom-testing-library + ...[ + 'ByShadowLabelText', + 'ByShadowPlaceholderText', + 'ByShadowText', + 'ByShadowAltText', + 'ByShadowTitle', + 'ByShadowDisplayValue', + 'ByShadowRole', + 'ByShadowTestId', + ].flatMap((query) => + createInvalidTestCase('shadow-dom-testing-library', query) + ), + ], }); diff --git a/tests/rules/prefer-find-by.test.ts b/tests/rules/prefer-find-by.test.ts index e1b2b8d2..ae226d04 100644 --- a/tests/rules/prefer-find-by.test.ts +++ b/tests/rules/prefer-find-by.test.ts @@ -19,6 +19,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; function buildFindByMethod(queryMethod: string) { diff --git a/tests/rules/prefer-query-by-disappearance.test.ts b/tests/rules/prefer-query-by-disappearance.test.ts index 4700299f..59a71abd 100644 --- a/tests/rules/prefer-query-by-disappearance.test.ts +++ b/tests/rules/prefer-query-by-disappearance.test.ts @@ -9,6 +9,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; ruleTester.run(rule.name, rule, { diff --git a/tests/rules/prefer-screen-queries.test.ts b/tests/rules/prefer-screen-queries.test.ts index 6d60153d..84396a9c 100644 --- a/tests/rules/prefer-screen-queries.test.ts +++ b/tests/rules/prefer-screen-queries.test.ts @@ -14,6 +14,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', + 'shadow-dom-testing-library', ]; const CUSTOM_QUERY_COMBINATIONS = combineQueries(ALL_QUERIES_VARIANTS, [ diff --git a/tests/utils/is-testing-library-module.test.ts b/tests/utils/is-testing-library-module.test.ts index 84edc728..7b2fe8d7 100644 --- a/tests/utils/is-testing-library-module.test.ts +++ b/tests/utils/is-testing-library-module.test.ts @@ -1,4 +1,4 @@ -import { it, expect, describe } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { isCustomTestingLibraryModule, @@ -20,6 +20,7 @@ const LIBRARY_MODULES = [ '@testing-library/vue', '@testing-library/svelte', '@marko/testing-library', + 'shadow-dom-testing-library', ] as const; const USER_EVENT_MODULE = '@testing-library/user-event'; From 1a783f78929082e8b657b8414b693cc4f6deacb1 Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 30 Mar 2026 19:03:36 +0200 Subject: [PATCH 2/6] fix: no-test-id --- src/rules/no-test-id-queries.ts | 7 ++++++- tests/rules/no-test-id-queries.test.ts | 14 ++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/rules/no-test-id-queries.ts b/src/rules/no-test-id-queries.ts index 88f79e84..556eebc1 100644 --- a/src/rules/no-test-id-queries.ts +++ b/src/rules/no-test-id-queries.ts @@ -7,7 +7,12 @@ const RULE_NAME = 'no-test-id-queries'; export type MessageIds = 'noTestIdQueries'; type Options = []; -const QUERIES_REGEX = `/^(${ALL_QUERIES_VARIANTS.join('|')})TestId$/`; +const QUERY_VARIANTS = [ + ...ALL_QUERIES_VARIANTS, + ...ALL_QUERIES_VARIANTS.map((query) => `${query}Shadow` as const), +]; + +const QUERIES_REGEX = `/^(${QUERY_VARIANTS.join('|')})TestId$/`; export default createTestingLibraryRule({ name: RULE_NAME, diff --git a/tests/rules/no-test-id-queries.test.ts b/tests/rules/no-test-id-queries.test.ts index f1e75555..1aa0debd 100644 --- a/tests/rules/no-test-id-queries.test.ts +++ b/tests/rules/no-test-id-queries.test.ts @@ -91,14 +91,12 @@ ruleTester.run(rule.name, rule, { ), // special cases for shadow-dom-testing-library ...[ - 'ByShadowLabelText', - 'ByShadowPlaceholderText', - 'ByShadowText', - 'ByShadowAltText', - 'ByShadowTitle', - 'ByShadowDisplayValue', - 'ByShadowRole', - 'ByShadowTestId', + 'getByShadowTestId', + 'queryByShadowTestId', + 'getAllByShadowTestId', + 'queryAllByShadowTestId', + 'findByShadowTestId', + 'findAllByShadowTestId', ].flatMap((query) => createInvalidTestCase('shadow-dom-testing-library', query) ), From e5f9d068871721d69ef802f7dfb1434c45343dc8 Mon Sep 17 00:00:00 2001 From: Sysix Date: Mon, 30 Mar 2026 19:47:55 +0200 Subject: [PATCH 3/6] refactor: uncomment for await-async-utils test --- tests/rules/await-async-utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rules/await-async-utils.test.ts b/tests/rules/await-async-utils.test.ts index 89cd9ed9..d1633c9c 100644 --- a/tests/rules/await-async-utils.test.ts +++ b/tests/rules/await-async-utils.test.ts @@ -19,7 +19,7 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', - 'shadow-dom-testing-library', + // 'shadow-dom-testing-library', does not export waitFor and waitForElementToBeRemoved utils, so not relevant to this rule ]; ruleTester.run(rule.name, rule, { From 12c7ccfa6b46e77d6741c3c2bfd4151b570fc4a9 Mon Sep 17 00:00:00 2001 From: "Alexander S." Date: Thu, 9 Apr 2026 21:47:48 +0200 Subject: [PATCH 4/6] Apply suggestion from @Sysix Signed-off-by: Alexander S. --- tests/rules/prefer-find-by.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/rules/prefer-find-by.test.ts b/tests/rules/prefer-find-by.test.ts index ae226d04..288eef80 100644 --- a/tests/rules/prefer-find-by.test.ts +++ b/tests/rules/prefer-find-by.test.ts @@ -19,7 +19,10 @@ const SUPPORTED_TESTING_FRAMEWORKS = [ '@testing-library/react', '@testing-library/vue', '@marko/testing-library', - 'shadow-dom-testing-library', + // list of supported frameworks, but not tested + // this test suite has too much test cases already, + // so we are skipping some of them to avoid making it even bigger + // 'shadow-dom-testing-library' ]; function buildFindByMethod(queryMethod: string) { From 79cf7487d80679fa8f42583677cc64f5b5a0b74c Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 12 Apr 2026 17:58:29 +0200 Subject: [PATCH 5/6] ci: --maxWorkers=2 for ci test --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index aa5f2d76..eb175a1c 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "eslint-remote-tester": "eslint-remote-tester --config eslint-remote-tester.config.ts", "semantic-release": "semantic-release", "test": "vitest run", - "test:ci": "vitest run --coverage", + "test:ci": "vitest run --coverage --maxWorkers=2", "test:ui": "vitest --ui", "test:watch": "vitest", "type-check": "tsc --noEmit" @@ -62,10 +62,10 @@ "@commitlint/config-conventional": "20.5.0", "@eslint/eslintrc": "3.3.5", "@eslint/js": "9.35.0", - "@types/node": "22.19.17", + "@types/node": "22.19.15", "@typescript-eslint/rule-tester": "^8.56.0", "@vitest/coverage-v8": "3.2.4", - "@vitest/eslint-plugin": "1.6.14", + "@vitest/eslint-plugin": "1.6.13", "@vitest/ui": "3.2.4", "eslint": "9.35.0", "eslint-config-prettier": "10.1.8", From 100f9e436c9debf387993551862220fa1aa4013f Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 12 Apr 2026 17:59:50 +0200 Subject: [PATCH 6/6] fix: after merge --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 677a2335..fbc97ba9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@commitlint/config-conventional": "20.5.0", "@eslint/eslintrc": "3.3.5", "@eslint/js": "9.35.0", - "@types/node": "22.19.15", + "@types/node": "22.19.17", "@typescript-eslint/rule-tester": "^8.56.0", "@vitest/coverage-v8": "3.2.4", "@vitest/eslint-plugin": "1.6.15",