@rvoh/psychic-spec-helpers 3.1.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,8 +32,8 @@ export default function providePuppeteerViteMatchers() {
32
32
  async toMatchTextContent(page, text, opts) {
33
33
  return await toMatchTextContent(page, text, opts);
34
34
  },
35
- async toNotMatchTextContent(page, text) {
36
- return await toNotMatchTextContent(page, text);
35
+ async toNotMatchTextContent(page, text, opts) {
36
+ return await toNotMatchTextContent(page, text, opts);
37
37
  },
38
38
  async toHaveSelector(page, cssSelector, opts) {
39
39
  return await toHaveSelector(page, cssSelector, opts);
@@ -1,19 +1,35 @@
1
- export default async function getAllTextContentFromPage(page) {
1
+ export default async function getAllTextContentFromPage(page, selector = 'body') {
2
2
  // Evaluate and extract all text content on the page
3
- const allText = await page.evaluate(() => {
3
+ const allText = await page.evaluate(selector => {
4
4
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
5
  // @ts-ignore
6
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
7
- const elements = document.body.querySelectorAll('*');
6
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
7
+ const roots = Array.from(document.querySelectorAll(selector));
8
8
  const textContentArray = [];
9
- elements.forEach(element => {
10
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
11
- if (element.textContent.trim() !== '') {
12
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
13
- textContentArray.push(element.innerText.trim());
14
- }
9
+ roots.forEach(root => {
10
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
11
+ const elements = [root, ...Array.from(root.querySelectorAll('*'))];
12
+ elements.forEach(element => {
13
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
14
+ const tagName = String(element.tagName || '').toLowerCase();
15
+ if (['script', 'style', 'noscript'].includes(tagName))
16
+ return;
17
+ let elementText;
18
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
19
+ if (typeof element.innerText === 'string') {
20
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
21
+ elementText = element.innerText;
22
+ }
23
+ else {
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
25
+ elementText = element.textContent;
26
+ }
27
+ const normalizedText = elementText?.trim();
28
+ if (normalizedText)
29
+ textContentArray.push(normalizedText);
30
+ });
15
31
  });
16
32
  return textContentArray.join(' ');
17
- });
33
+ }, selector);
18
34
  return allText;
19
35
  }
@@ -1,23 +1,27 @@
1
- import applyDefaultWaitForOpts from '../helpers/applyDefaultWaitForOpts.js';
2
- export default async function toMatchTextContent(page, text, opts = {}) {
3
- try {
4
- await page.waitForSelector(`${opts.selector || 'body'}::-p-text(${text.replace(/"/g, '\\"')})`, applyDefaultWaitForOpts(opts));
1
+ import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js';
2
+ import getAllTextContentFromPage from '../internal/getAllTextContentFromPage.js';
3
+ import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
4
+ export default async function toMatchTextContent(argumentPassedToExpect, expected, opts = {}) {
5
+ return await evaluateWithRetryAndTimeout(argumentPassedToExpect, async () => {
6
+ requirePuppeteerPage(argumentPassedToExpect);
7
+ const actual = await getAllTextContentFromPage(argumentPassedToExpect, opts.selector);
8
+ if (expected instanceof RegExp)
9
+ expected.lastIndex = 0;
5
10
  return {
6
- pass: true,
7
- message: () => {
8
- throw new Error('Cannot negate toMatchTextContent, use toNotMatchTextContent instead');
9
- },
11
+ pass: typeof expected === 'string' ? actual.includes(expected) : expected.test(actual),
12
+ actual,
10
13
  };
11
- }
12
- catch {
13
- return {
14
- pass: false,
15
- message: () => `
14
+ }, {
15
+ successText: () => {
16
+ throw new Error('Cannot negate toMatchTextContent, use toNotMatchTextContent instead');
17
+ },
18
+ failureText: actual => `
16
19
  expected ${opts.selector || 'body'} with text:
17
- ${text}
20
+ ${expected.toString()}
18
21
 
19
- but no text was found within that selector
22
+ but no matching text was found within that selector:
23
+ ${actual}
20
24
  `,
21
- };
22
- }
25
+ timeout: opts.timeout,
26
+ });
23
27
  }
@@ -4,16 +4,18 @@ import requirePuppeteerPage from '../internal/requirePuppeteerPage.js';
4
4
  export default async function toNotMatchTextContent(argumentPassedToExpect, expected, opts = {}) {
5
5
  return await evaluateWithRetryAndTimeout(argumentPassedToExpect, async () => {
6
6
  requirePuppeteerPage(argumentPassedToExpect);
7
- const actual = await getAllTextContentFromPage(argumentPassedToExpect);
7
+ const actual = await getAllTextContentFromPage(argumentPassedToExpect, opts.selector);
8
+ if (expected instanceof RegExp)
9
+ expected.lastIndex = 0;
8
10
  return {
9
- pass: !actual.includes(expected),
11
+ pass: typeof expected === 'string' ? !actual.includes(expected) : !expected.test(actual),
10
12
  actual,
11
13
  };
12
14
  }, {
13
15
  successText: () => {
14
16
  throw new Error('Cannot negate toNotMatchTextContent, use toMatchTextContent instead');
15
17
  },
16
- failureText: r => `Expected ${r} to not match text ${expected}, but it did`,
18
+ failureText: r => `Expected ${r} to not match text ${expected.toString()}, but it did`,
17
19
  timeout: opts.timeout,
18
20
  });
19
21
  }
@@ -1,2 +1,2 @@
1
1
  import { Page } from 'puppeteer';
2
- export default function getAllTextContentFromPage(page: Page): Promise<string>;
2
+ export default function getAllTextContentFromPage(page: Page, selector?: string): Promise<string>;
@@ -1,7 +1,9 @@
1
1
  import { Page, WaitForSelectorOptions } from 'puppeteer';
2
- export default function toMatchTextContent(page: Page, text: string, opts?: {
2
+ export type TextContentMatcherExpected = string | RegExp;
3
+ export type TextContentMatcherOpts = {
3
4
  selector?: string;
4
- } & WaitForSelectorOptions): Promise<{
5
- pass: boolean;
5
+ } & WaitForSelectorOptions;
6
+ export default function toMatchTextContent(argumentPassedToExpect: Page, expected: TextContentMatcherExpected, opts?: TextContentMatcherOpts): Promise<{
6
7
  message: () => string;
8
+ pass: boolean;
7
9
  }>;
@@ -1,5 +1,6 @@
1
- import { Page, WaitForSelectorOptions } from 'puppeteer';
2
- export default function toNotMatchTextContent(argumentPassedToExpect: Page, expected: string, opts?: WaitForSelectorOptions): Promise<{
1
+ import { Page } from 'puppeteer';
2
+ import type { TextContentMatcherExpected, TextContentMatcherOpts } from './toMatchTextContent.js';
3
+ export default function toNotMatchTextContent(argumentPassedToExpect: Page, expected: TextContentMatcherExpected, opts?: TextContentMatcherOpts): Promise<{
3
4
  message: () => string;
4
5
  pass: boolean;
5
6
  }>;
@@ -2,6 +2,7 @@ import { Page, WaitForSelectorOptions } from 'puppeteer';
2
2
  import { CustomMatcherResult } from './feature/helpers/providePuppeteerViteMatchers.js';
3
3
  import { ExpectToEvaluateOpts } from './feature/internal/evaluateWithRetryAndTimeout.js';
4
4
  import { ToFillMatcherOpts } from './feature/matchers/toFill.js';
5
+ import type { TextContentMatcherExpected, TextContentMatcherOpts } from './feature/matchers/toMatchTextContent.js';
5
6
  export { RequestBody as OpenapiRequestBody, RequestQueryParameters as OpenapiRequestQuery, ResponseBody as OpenapiResponseBody, ResponseCodeForUri as OpenapiResponseCodeForUri, } from './unit/helpers/openapiTypeHelpers.js';
6
7
  export { DreamRequestAttributes } from './unit/helpers/typeHelpers.js';
7
8
  export { default as createPsychicServer } from './unit/createPsychicServer.js';
@@ -39,8 +40,8 @@ interface PuppeteerAssertions {
39
40
  toMatchDreamModels(expected: any): CustomMatcherResult;
40
41
  toBeWithin(precision: number, expected: number): CustomMatcherResult;
41
42
  toEqualCalendarDate(expected: any): CustomMatcherResult;
42
- toMatchTextContent(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>;
43
- toNotMatchTextContent(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>;
43
+ toMatchTextContent(expected: TextContentMatcherExpected, opts?: TextContentMatcherOpts): Promise<CustomMatcherResult>;
44
+ toNotMatchTextContent(expected: TextContentMatcherExpected, opts?: TextContentMatcherOpts): Promise<CustomMatcherResult>;
44
45
  toHaveSelector(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>;
45
46
  toNotHaveSelector(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>;
46
47
  toCheck(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@rvoh/psychic-spec-helpers",
4
- "version": "3.1.1",
4
+ "version": "3.2.0",
5
5
  "description": "psychic framework spec helpers",
6
6
  "author": "RVO Health",
7
7
  "repository": {
@@ -15,6 +15,10 @@ import toHavePath from '../matchers/toHavePath.js'
15
15
  import toHaveSelector from '../matchers/toHaveSelector.js'
16
16
  import toHaveUnchecked from '../matchers/toHaveUnchecked.js'
17
17
  import toHaveUrl from '../matchers/toHaveUrl.js'
18
+ import type {
19
+ TextContentMatcherExpected,
20
+ TextContentMatcherOpts,
21
+ } from '../matchers/toMatchTextContent.js'
18
22
  import toMatchTextContent from '../matchers/toMatchTextContent.js'
19
23
  import toNotHaveSelector from '../matchers/toNotHaveSelector.js'
20
24
  import toNotMatchTextContent from '../matchers/toNotMatchTextContent.js'
@@ -35,14 +39,18 @@ export default function providePuppeteerViteMatchers() {
35
39
  ;(global as any).expect.extend({
36
40
  async toMatchTextContent(
37
41
  page: Page,
38
- text: string,
39
- opts?: { selector?: string } & WaitForSelectorOptions
42
+ text: TextContentMatcherExpected,
43
+ opts?: TextContentMatcherOpts
40
44
  ) {
41
45
  return await toMatchTextContent(page, text, opts)
42
46
  },
43
47
 
44
- async toNotMatchTextContent(page: Page, text: string) {
45
- return await toNotMatchTextContent(page, text)
48
+ async toNotMatchTextContent(
49
+ page: Page,
50
+ text: TextContentMatcherExpected,
51
+ opts?: TextContentMatcherOpts
52
+ ) {
53
+ return await toNotMatchTextContent(page, text, opts)
46
54
  },
47
55
 
48
56
  async toHaveSelector(page: Page, cssSelector: string, opts?: WaitForSelectorOptions) {
@@ -1,26 +1,42 @@
1
1
  import { Page } from 'puppeteer'
2
2
 
3
- export default async function getAllTextContentFromPage(page: Page) {
3
+ export default async function getAllTextContentFromPage(page: Page, selector = 'body') {
4
4
  // Evaluate and extract all text content on the page
5
- const allText = await page.evaluate(() => {
5
+ const allText = await page.evaluate(selector => {
6
6
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
7
7
  // @ts-ignore
8
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
9
- const elements = document.body.querySelectorAll('*')
8
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
9
+ const roots = Array.from(document.querySelectorAll(selector))
10
10
 
11
11
  const textContentArray: string[] = []
12
12
 
13
13
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
- ;(elements as any[]).forEach(element => {
15
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
16
- if (element.textContent.trim() !== '') {
17
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
18
- textContentArray.push(element.innerText.trim())
19
- }
14
+ ;(roots as any[]).forEach(root => {
15
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
16
+ const elements = [root, ...Array.from(root.querySelectorAll('*'))]
17
+
18
+ elements.forEach(element => {
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
20
+ const tagName = String(element.tagName || '').toLowerCase()
21
+ if (['script', 'style', 'noscript'].includes(tagName)) return
22
+
23
+ let elementText: string | undefined
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
25
+ if (typeof element.innerText === 'string') {
26
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
27
+ elementText = element.innerText
28
+ } else {
29
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
30
+ elementText = element.textContent
31
+ }
32
+
33
+ const normalizedText = elementText?.trim()
34
+ if (normalizedText) textContentArray.push(normalizedText)
35
+ })
20
36
  })
21
37
 
22
38
  return textContentArray.join(' ')
23
- })
39
+ }, selector)
24
40
 
25
41
  return allText
26
42
  }
@@ -1,31 +1,41 @@
1
1
  import { Page, WaitForSelectorOptions } from 'puppeteer'
2
- import applyDefaultWaitForOpts from '../helpers/applyDefaultWaitForOpts.js'
2
+ import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js'
3
+ import getAllTextContentFromPage from '../internal/getAllTextContentFromPage.js'
4
+ import requirePuppeteerPage from '../internal/requirePuppeteerPage.js'
5
+
6
+ export type TextContentMatcherExpected = string | RegExp
7
+ export type TextContentMatcherOpts = { selector?: string } & WaitForSelectorOptions
3
8
 
4
9
  export default async function toMatchTextContent(
5
- page: Page,
6
- text: string,
7
- opts: { selector?: string } & WaitForSelectorOptions = {}
10
+ argumentPassedToExpect: Page,
11
+ expected: TextContentMatcherExpected,
12
+ opts: TextContentMatcherOpts = {}
8
13
  ) {
9
- try {
10
- await page.waitForSelector(
11
- `${opts.selector || 'body'}::-p-text(${text.replace(/"/g, '\\"')})`,
12
- applyDefaultWaitForOpts(opts)
13
- )
14
- return {
15
- pass: true,
16
- message: () => {
14
+ return await evaluateWithRetryAndTimeout(
15
+ argumentPassedToExpect,
16
+ async () => {
17
+ requirePuppeteerPage(argumentPassedToExpect)
18
+
19
+ const actual = await getAllTextContentFromPage(argumentPassedToExpect, opts.selector)
20
+ if (expected instanceof RegExp) expected.lastIndex = 0
21
+
22
+ return {
23
+ pass: typeof expected === 'string' ? actual.includes(expected) : expected.test(actual),
24
+ actual,
25
+ }
26
+ },
27
+ {
28
+ successText: () => {
17
29
  throw new Error('Cannot negate toMatchTextContent, use toNotMatchTextContent instead')
18
30
  },
19
- }
20
- } catch {
21
- return {
22
- pass: false,
23
- message: () => `
31
+ failureText: actual => `
24
32
  expected ${opts.selector || 'body'} with text:
25
- ${text}
33
+ ${expected.toString()}
26
34
 
27
- but no text was found within that selector
35
+ but no matching text was found within that selector:
36
+ ${actual}
28
37
  `,
38
+ timeout: opts.timeout,
29
39
  }
30
- }
40
+ )
31
41
  }
@@ -1,21 +1,24 @@
1
- import { Page, WaitForSelectorOptions } from 'puppeteer'
1
+ import { Page } from 'puppeteer'
2
2
  import evaluateWithRetryAndTimeout from '../internal/evaluateWithRetryAndTimeout.js'
3
3
  import getAllTextContentFromPage from '../internal/getAllTextContentFromPage.js'
4
4
  import requirePuppeteerPage from '../internal/requirePuppeteerPage.js'
5
+ import type { TextContentMatcherExpected, TextContentMatcherOpts } from './toMatchTextContent.js'
5
6
 
6
7
  export default async function toNotMatchTextContent(
7
8
  argumentPassedToExpect: Page,
8
- expected: string,
9
- opts: WaitForSelectorOptions = {}
9
+ expected: TextContentMatcherExpected,
10
+ opts: TextContentMatcherOpts = {}
10
11
  ) {
11
12
  return await evaluateWithRetryAndTimeout(
12
13
  argumentPassedToExpect,
13
14
  async () => {
14
15
  requirePuppeteerPage(argumentPassedToExpect)
15
16
 
16
- const actual = await getAllTextContentFromPage(argumentPassedToExpect)
17
+ const actual = await getAllTextContentFromPage(argumentPassedToExpect, opts.selector)
18
+ if (expected instanceof RegExp) expected.lastIndex = 0
19
+
17
20
  return {
18
- pass: !actual.includes(expected),
21
+ pass: typeof expected === 'string' ? !actual.includes(expected) : !expected.test(actual),
19
22
  actual,
20
23
  }
21
24
  },
@@ -23,7 +26,7 @@ export default async function toNotMatchTextContent(
23
26
  successText: () => {
24
27
  throw new Error('Cannot negate toNotMatchTextContent, use toMatchTextContent instead')
25
28
  },
26
- failureText: r => `Expected ${r} to not match text ${expected}, but it did`,
29
+ failureText: r => `Expected ${r} to not match text ${expected.toString()}, but it did`,
27
30
  timeout: opts.timeout,
28
31
  }
29
32
  )
package/src/index.ts CHANGED
@@ -2,6 +2,10 @@ import { Page, WaitForSelectorOptions } from 'puppeteer'
2
2
  import { CustomMatcherResult } from './feature/helpers/providePuppeteerViteMatchers.js'
3
3
  import { ExpectToEvaluateOpts } from './feature/internal/evaluateWithRetryAndTimeout.js'
4
4
  import { ToFillMatcherOpts } from './feature/matchers/toFill.js'
5
+ import type {
6
+ TextContentMatcherExpected,
7
+ TextContentMatcherOpts,
8
+ } from './feature/matchers/toMatchTextContent.js'
5
9
  export {
6
10
  RequestBody as OpenapiRequestBody,
7
11
  RequestQueryParameters as OpenapiRequestQuery,
@@ -64,10 +68,14 @@ interface PuppeteerAssertions {
64
68
  toEqualCalendarDate(expected: any): CustomMatcherResult
65
69
 
66
70
  // begin: fspec matchers
67
- // eslint-disable-next-line
68
- toMatchTextContent(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>
69
- // eslint-disable-next-line
70
- toNotMatchTextContent(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>
71
+ toMatchTextContent(
72
+ expected: TextContentMatcherExpected,
73
+ opts?: TextContentMatcherOpts
74
+ ): Promise<CustomMatcherResult>
75
+ toNotMatchTextContent(
76
+ expected: TextContentMatcherExpected,
77
+ opts?: TextContentMatcherOpts
78
+ ): Promise<CustomMatcherResult>
71
79
  // eslint-disable-next-line
72
80
  toHaveSelector(expected: any, opts?: WaitForSelectorOptions): Promise<CustomMatcherResult>
73
81
  // eslint-disable-next-line