Skip to content

Commit

Permalink
DB-6542: [gatsby-wp, gatsby-wp-acf-addon] Use TypeScript for the `gat…
Browse files Browse the repository at this point in the history
…sby-wp*` templates (#794)

* DB-5244: Add tagged template support in addWithDiff + tests

* DB-5244: Add tagged template support in addWithDiff + tests

* DB-5244: gatsby-wp ts conversion

- Refactor gatsby-node.js and gatsby-config.js to ts
- Add gatsby graphql generated types
- Split out gatsby-node create page function to lib/createPagesUtils
- Remove redundant page queries, use pageContext instead
- Refactor page and post componentsto ts

* DB-5244: Convert 404 page and seo component to ts

* DB-5244: Convert pagination component and template to ts

- Break out some logic to a custom hook

* DB-5244: Clean up page template

* DB-5244: Fix component type and ts for paginationExample template

* DB-5244: More types cleanup

* DB-5244: Use default filepath for generated gatsby-types.d.ts

* DB-5244: Convert remaining pages to ts

* DB-5244:

- Ignore linting for static templates
- Fix memory leak in watch script

* DB-5244: Replace tests with single example for snapshots and unit testing

* DB-5244: Update test section in the README

* DB-5244: Fix getPages and getPosts types

* DB-5244: Move generator data interfaces to types.ts

This allows the data object in the tagged templates to make use
of the types. There is likely some more work to be done here
to further enhance the types for the generators.

* DB-5244: Convert remaining handlebars templates to new tagged templates

- Removed some tagged template helpers where backticks can be used instead to greater effect
- Added a @partials alias to the root tsconfig for easier importing of new partials

Partials are written in TypeScript and do not use the TemplateFn signature,
but instead accept only the variables they need to render out the proper string.
These partials can be imported as functions and called as needed inside
tagged template literal templates.

* DB-5244: Replace handlebars instructions with new tagged templates in CONTRIBUTING

* DB-5244: Add changeset

* DB-5244: Sanitize innerHTML

* DB-5244: Undo change to nextjs-kit

* DB-5244: Compile tagged templates to js

- Moved typescript path alias to tsconfig in create- package
- Add esbuild-ts-paths to resolve the path aliases
- Rename the shared partials as the name was conflicting with
the handlebars templates during the build
- Update the copyTemplates script to ignore the templates that
are to be compiled
- Move the tagged template regex to a shareable constants.ts file in utils
- Updated path alias imports with new path

* DB-5244: Fix linting and build errors, re-add wpMenu.json for test example

- Made the types on Input optional. I'm not a huge fan of this but
at this point I'm not sure the best path forward to type both
the generator data field and the same data coming into the templates.
- Fixed tests
- Added new test
- Ensure .tsx files are converted in the ConvertCSSModulesaction

* DB-5244: Use isometric-dompurify to fix build errors

* DB-5244:

- Remove stray console.logs
- Typecast some things in post & page to a string
- Re-add @types/dompurify
- Update lando.yml node version to 18

* DB-5244: Ensure gatsby-*.tsx files are included in tsconfig

* DB-5244: Fix wpMenu.json import

* DB-6357: Upgrade gatsby-wp templates to gastby v5

* DB-6357: Add changeset

* DB-6357: Pin convert-css-modules-to-tailwind to 0.1.9

* DB-6357: Fix indentation

* DB-6357: Remove unused packages

* DB-6357: Touch up CSS, fix broken css from modules->tw conversion

* DB-6357: Update glob in lint-staged for tagged templates

* DB-6357: Move min-height to paginatorExample template

* DB-6357: Remove componentContainer

* DB-5578:

- Move wpPostQuery to a partial
- Move postTemplate to a partial
- Use new template style for postTemplate.module.css
- Add postTemplate.module.css.d.ts
- Add update generated gatsby-types.d.ts for the related content queries
- Remove beforeExit listener from main()
- Refine types for PostGridItem and PageGridItem
- Source relatedContent from pageContext, replacing the pageQuery
- Make lint-staged.config explicit for tagged template file extensions
to avoid failing on *.module.css.d.ts files
- Add changeset

* Exit pre-release mode for sprint 12

* Update versions

* Enter pre-release mode for sprint 13

* Nightly audit run at 00:05:34

* Add changeset to bump nextjs-kit version in the CLI

* DB-6330:

- Remove check-built script
- Update types path in PantheonDrupalSate
- Bump and pin @gdwc/drupal-state
- Add jsdom to nextjs kit, tests were suddenly failing without it
- Add changesets

* Update healthy-numbers-judge.md

* DB-6330:

- Move setOutgoingHeaders to wordpress-kit
- Update next-wp templates reflect setOutgoingHeaders change
- Update code snippet in docs to use setOutgoingHeaders
- Add tests

* DB-6331: Match @types/react-dom to @types/react

* canary-release (canary)

* Next config chanege no longer necessary for Drupal surrogate key example

* DB-6331: Remove wordpress-kit from transpilePackage

The starter no longer requires this package to be transpiled by
nextjs and this is also breaking on some versions of next

* canary-release (canary)

* DB-5777: developer docs for terminus-decoupled-kit plugin

* DB-6331: Pin starters to [email protected]

* canary-release (canary)

* DB-5777: clarify use of upstream ID based on feedback

* DB-5777: remove unneded -

* Exit pre-release mode for sprint 13

* Update versions

* Enter pre-release for sprint 14

* DB-6269: Scaffold health-check package

* DB-6269:

- Health check script for drupal starter

* DB-6269: Refactor to only check one endpoint

- Add typedoc comments

* DB-6269: Setup msw for tests

* DB-6269: Setup vitest and begin adding unit tests

* DB-6269: Use `msw@next` for node 18 fetch compatibility

* DB-6269: Add tests

* DB-6269: Add changeset, adjust package.json version for first release

* DB-6269: Update README, fix shebang

* DB-6269: Remove unused constants.ts

* DB-6269: Rename health-check -> decoupled-kit-health-check

* DB-6269:

- Refine error messages
- Remove hourglass emoji
- Replace do-not-enter emoji with hourglass for log.warn
- Remove emoji for log.suggest
- Add checkPreviewSecret function
- Add engines field to package.json
- Update pnpm-lock.yaml

* DB-6269: Update changeset with new name

* canary-release (canary)

* DB-6269:

- Add decoupled-kit-health-check as devDependency to next-drupal
- Run the health-check as the first step of the build command

* canary-release (canary)

* DB-6269: Fix missing https in PANTHEON_CMS_ENDPOINT

* canary-release (canary)

* DB-6540:

- Add health-check script for next-wp
- Read arg from CLI to determine which script to run (drupal or wp)
- Check package.json to determine which script to run (next or gatsby)
- Update functions to work for drupal (rest) and wp (graphql)
- Update tests accordingly
- Add msw handlers for wordpress
- Add changeset
- Add script to next-wp package.json

* DB-6450: Make cmsEndpoint a function to avoid polluting search params between requests

* DB-6450:

- Update README
- Update README for next-wp and next-drupal starters
- Add section in troubleshooting next+drupal and next+wp about disabling
the health-check
- Throw an error if no arg is present when running the command

* canary-release (canary)

* Fix next-wp package.json

* canary-release (canary)

* DB-6450: Ensure endpoint ends with /wp/graphql

* canary-release (canary)

* Nightly audit run at 00:05:46

* DB-4444: next-drupal preview route test option

* DB-6463:

- Respond to test=true query param with json in the next-wp preview api route
- Update next-drupal preivew test responses to proper status codes

* DB-6463: Add and edit changesets

* DB-6463: Remove console.log

* canary-release (canary)

* refactor: Use classes for better readability and extensibility

* refactor: Use snapshots for tests

* Add changeset

* Nightly audit run at 00:05:31

* Upgrade tooling dependencies

[root]: typescript, typedoc, prettier
[workspace-eslint]: @typescript/eslint-plugin, @typescript/eslint-parser
[wp-kit, drupal-kit]:  msw - required for typescript 5.1

Note: The published configs have not had their dependencies updated.
These will be updated in a future ticket when we do a more comprehensive
dependency update.

* DB-6542:

- Rebase feat/typescript
- Refine linting scripts for templates and non templates
This should ensure the tagged templates ending in .ts are linted
but other templates are not.
- Remove some options from runLint that we do not use

* DB-6542: Add changeset

* DB-6542:

- Use .ts for vite.config
- Fix missing backticks around posts query
- Use tailwindcss version for example snapshot test

---------

Co-authored-by: Mitchell Markoff <[email protected]>
Co-authored-by: Brian Perry <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: pantheon-decoupled-service-user <[email protected]>
  • Loading branch information
5 people authored Jul 24, 2023
1 parent edf7ed3 commit 71c8441
Show file tree
Hide file tree
Showing 160 changed files with 31,104 additions and 5,169 deletions.
6 changes: 6 additions & 0 deletions .changeset/five-tools-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'create-pantheon-decoupled-kit': minor
---

[gatsby-wp] Use TypeScript by default
[cli] Begin transitioning to tagged template literal templates away from handlebars
5 changes: 5 additions & 0 deletions .changeset/gorgeous-terms-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'create-pantheon-decoupled-kit': patch
---

[gatsby-wp] Upgraded the gatsby-wp templates to use gatsby v5
6 changes: 6 additions & 0 deletions .changeset/spicy-schools-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'create-pantheon-decoupled-kit': minor
---

[gatsby-wp] Starters created using the `gatsby-wp` generator are now in
TypeScript
7 changes: 7 additions & 0 deletions .changeset/spicy-seals-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'create-pantheon-decoupled-kit': patch
---

[gatsby-wp-acf-addon] Rewrite templates in TypeScript

[gatsby-wp] Move some templates to partials
8 changes: 8 additions & 0 deletions .changeset/tiny-bugs-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'create-pantheon-decoupled-kit': patch
---

Add support for tagged template literal templates in addition to handlebars
templates. This is in effort to smooth out developer experience when writing
templates for the generators. This change is meant to be backwards compatible so
handlebars templates should still work.
5 changes: 2 additions & 3 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ coverage
dist
public

*.d.ts

# handlebars templates
*.hbs
**/__tests__/fixtures/**
**/__tests__/fixtures/**
**/*.snap
143 changes: 83 additions & 60 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,24 @@ To create the project with the `next-drupal-umami` demo data, use the
pnpm create pantheon-decoupled-kit next-drupal next-drupal-umami-addon --appName my-next-drupal-umami --outDir ./next-drupal-umami
```

### Creating a Generator
### Templates

New templates are written using
[tagged template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals).
A template does not need to be dynamic. Static templates should use the normal
file extension for that file. Dynamic templates should export a default function
with the `TemplateFn` signature which can be seen in detail at
`create-pantheon-decoupled-kit/src/types.ts`. These files will end in two file
extensions: the desired extension and .ts. So for a `.tsx` file, the filename
for the template would look like `templateName.tsx.ts`. When the generators are
run, the `addWithDiff` action will run the default function and remove the `.ts`
extension from the filename, writing the contents to `templateName.tsx` in the
desired output location.

Some old templates are written using handlebars. These templates will be
replaced with the new tagged template pattern.

Templates are written in the
[handlebars templating language](https://handlebarsjs.com/). A template does not
need to be dynamic. Static templates should not use the handlebars file
extension `.hbs`, but instead should be included in the template directory as
is.
### Creating a Generator

A custom generator should satisfy the `DecoupledKitGenerator` type, as well as
the following criteria:
Expand Down Expand Up @@ -172,15 +183,19 @@ the `addon` field set to `true`.
One caveat to keep in mind when writing an add-on: templates that exist with the
same path in both an add-on and a project generator will favor the add-on.
Meaning, if there are two generators being run and both have an
`src/example/index.ts.hbs`, the add-on's `src/example/index.ts` will be the one
`src/example/index.ts.ts`, the add-on's `src/example/index.ts` will be the one
rendered.

### Adding Partials

[Handlebars Partials](https://handlebarsjs.com/guide/#partials) should be used
when possible to reduce duplication of code across multiple files. Partials must
be added to the `create-pantheon-decoupled-kit/src/templates/partials` directory
in order to be automatically registered to the handlebars instance.
New partials should be written in TypeScript. They should accept any arguments
required for that partial only.

```typescript
// create-pantheon-decoupled-kit/src/templates/partials/examples/myPartial.ts
export const myPartial = (someDynamicArg: string) =>
`console.log('${someDynamicArg}')`;
```

### Adding Custom Actions

Expand Down Expand Up @@ -241,50 +256,58 @@ and arbitrary data needed by the template:
* This example uses os to show how to conditionally
* and dynamically set data.
*/
import os from 'node:os'
import type { DefaultAnswers, DecoupledKitGenerator } from '../types.ts'
import os from 'node:os';
import type { DefaultAnswers, DecoupledKitGenerator } from '../types.ts';

interface ExampleAnswers extends DefaultAnswers {
requiredUserInput: string;
requiredUserInput: string;
}

interface ExampleData {
arbitraryBoolean: boolean;
arbitraryObject: {
someValue: string;
anotherValue: number;
},
optionalData?: string
arbitraryBoolean: boolean;
arbitraryObject: {
someValue: string;
anotherValue: number;
};
optionalData?: string;
}


export const exampleGenerator: DecoupledKitGenerator<ExampleAnswers, ExampleData> = {
name: 'decoupled-kit-example',
prompts: [
name: 'requiredUserInput',
message: 'This message is displayed when this prompt is run',
default: (({ outDir }: ExampleAnswers) => {
/**
* previous answers are available to make
* parts of the prompt dynamic if necessary
*/
return `${outDir.replaceAll('/', '-')}`
})
],
data: {
arbitraryBoolean: true,
arbitraryObject: {
// arbitrary data can be dynamic
someValue: os.arch(),
anotherValue: os.freemem()
}
// data can be added based on arbitrary conditions
...(os.platform() === 'darwin' && {
optionalData: 'This data will only be included if the OS platform is darwin'
})
},
templates: ['example-templates']
}
export const exampleGenerator: DecoupledKitGenerator<
ExampleAnswers,
ExampleData
> = {
name: 'decoupled-kit-example',
description: 'example generator',
prompts: [
{
name: 'requiredUserInput',
message: 'This message is displayed when this prompt is run',
default: ({ outDir }: ExampleAnswers) => {
/**
* previous answers are available to make
* parts of the prompt dynamic if necessary
*/
return `${outDir.replaceAll('/', '-')}`;
},
},
],
data: {
arbitraryBoolean: true,
arbitraryObject: {
// arbitrary data can be dynamic
someValue: os.arch(),
anotherValue: os.freemem(),
},
// data can be added based on arbitrary conditions
...(os.platform() === 'darwin' && {
optionalData:
'This data will only be included if the OS platform is darwin',
}),
},
templates: ['example-templates'],
actions: [addWithDiff],
cmsType: 'any',
};
```

The templates in the `templates` array will need to be defined in
Expand All @@ -294,19 +317,22 @@ renders the templates. An example of utilize this data could like something like
the following:

<!-- prettier-ignore -->
```handlebars
// src/templates/example-templates/index.ts.hbs
{{#unless arbitraryBoolean}}
```typescript
// src/templates/example-templates/index.ts.ts
import type { TemplateFn } from '@cli/types';

const ts: TemplateFn = ({ data, utils }) =>`
${utils.if(!arbitraryBoolean, `import { someModule } from '../somewhere';`)}}
import { someModule } from '../somewhere';
{{/unless}}
const {{arbitraryObject.someValue}} = '{{arbitraryObject.anotherValue}}'
const ${data.arbitraryObject.someValue} = '${data.arbitraryObject.anotherValue}'
${utils.if(data.optionalData, `console.log('${data.optionalData}')`)}
{{#if optionalData}}
console.log('{{optionalData}}')
{{/if}}
console.log('${requiredUserInput}')
`
export default ts;

console.log('{{requiredUserInput}}')
```

Stepping through this contrived example template:
Expand All @@ -316,9 +342,6 @@ Stepping through this contrived example template:
`arbitraryObject.anotherValue` as a string.
1. If `optionalData` is present, console.log the value of it.

Explore
[this template in the handlebars playground](<https://handlebarsjs.com/playground.html#format=1&currentExample=%7B%22template%22%3A%22%7B%7B%23unless%20arbitraryBoolean%7D%7D%5Cnimport%20%7B%20someModule%20%7D%20from%20'..%2Fsomewhere'%3B%5Cn%7B%7B%2Funless%7D%7D%5Cn%5Cnconst%20%7B%7BarbitraryObject.someValue%7D%7D%20%3D%20'%7B%7BarbitraryObject.anotherValue%7D%7D'%5Cn%5Cn%7B%7B%23if%20optionalData%7D%7D%5Cn%5Ctconsole.log('%7B%7BoptionalData%7D%7D')%5Cn%7B%7B%2Fif%7D%7D%5Cn%5Cnconsole.log('%7B%7BrequiredUserInput%7D%7D')%22%2C%22partials%22%3A%5B%5D%2C%22input%22%3A%22%7B%5Cn%5CtarbitraryBoolean%3A%20false%2C%5Cn%20%20%20%20arbitraryObject%3A%20%7B%5Cn%20%20%20%20%5CtsomeValue%3A%20'hello'%2C%5Cn%20%20%20%20%5CtanotherValue%3A%20'world'%2C%5Cn%20%20%7D%2C%5Cn%20%20optionalData%3A%20''%2C%5Cn%20%20requiredUserInput%3A%20'I%20am%20required!'%5Cn%20%20%20%20%5Cn%7D%5Cn%22%2C%22output%22%3A%22import%20%7B%20someModule%20%7D%20from%20'..%2Fsomewhere'%3B%5Cn%5Cnconst%20hello%20%3D%20'world'%5Cn%5Cn%5Cnconsole.log('I%20am%20required!')%22%2C%22preparationScript%22%3A%22%5Cn%22%2C%22handlebarsVersion%22%3A%224.7.7%22%7D>).

### The `watch` Script

The `watch` script enables a sort of "developer mode" for a generator's
Expand Down
4 changes: 3 additions & 1 deletion packages/create-pantheon-decoupled-kit/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
"parserOptions": {
"project": ["./tsconfig.json"]
},
"ignorePatterns": ["/src/templates/**/*.jsx"]
"rules": {
"@typescript-eslint/restrict-template-expressions": "warn"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const globalData = {
outDir: outDir('empty'),
force: false,
silent: false,
y: true,
z: false,
empty: true,
};

describe('addWithDiff()', () => {
Expand All @@ -46,6 +49,8 @@ test input
'test-withDiff.js': `console.log('Hello World')`,
'.gitignore':
'#this file should be renamed to .gitignore when it is written with the addWithDiff action',
'test-ts-template.ts': `const x = 'I am a string';
console.log(x);`,
};
await fs.ensureDir(path.resolve(outDir('populated')));
Object.keys(populated).forEach((key) => {
Expand Down Expand Up @@ -131,7 +136,7 @@ test input
templateData,
});
expect(handlebarsSpy).toHaveBeenCalledTimes(2);
expect(promptSpy).toHaveBeenCalledTimes(5);
expect(promptSpy).toHaveBeenCalledTimes(6);
});

it('should should a diff and prompt for each different file when the target exists', async ({
Expand All @@ -146,6 +151,7 @@ test input
data.outDir = outDir('populated');
data.anotherInput = 'Testing a different output';
data.diffInput = 'this input is also different';
data.z = true;
await actions.addWithDiff({ data, templateData, handlebars });

expect(addWithDiffSpy).toHaveBeenLastCalledWith({
Expand All @@ -154,7 +160,7 @@ test input
handlebars,
});
expect(handlebarsSpy).toHaveBeenCalledTimes(2);
expect(promptSpy).toHaveBeenCalledTimes(2);
expect(promptSpy).toHaveBeenCalledTimes(3);
});

it('should only prompt the user once if answer is "yes to all"', async ({
Expand Down Expand Up @@ -227,7 +233,7 @@ test input
await actions.addWithDiff({ data, templateData, handlebars });

expect(addWithDiffSpy).toHaveBeenCalledOnce();
expect(unlinkSyncSpy).toHaveBeenCalledTimes(5);
expect(unlinkSyncSpy).toHaveBeenCalledTimes(6);
});

it('should exit on abort', async ({ promptSpy, logSpy, addWithDiffSpy }) => {
Expand Down Expand Up @@ -260,4 +266,18 @@ test input
expect(promptSpy).toHaveBeenCalledTimes(1);
expect(fs.existsSync(`${outDir('empty')}/.gitignore`)).toBeTruthy();
});

it('should not render blank files', async ({ promptSpy, addWithDiffSpy }) => {
vi.mocked(inquirer.prompt).mockImplementation(async () => ({
writeFile: 'yes to all',
}));
const data = Object.assign({}, globalData);
data.outDir = outDir('populated');
const pathToFile = path.join(outDir('populated'), 'test-can-be-empty.ts');

await actions.addWithDiff({ data, templateData, handlebars });
expect(promptSpy).toHaveBeenCalledTimes(1);
expect(addWithDiffSpy).toHaveBeenCalled();
expect(fs.existsSync(pathToFile)).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('convertCSSModules()', () => {
vi.resetAllMocks();
});

it('should call css-modules-to-tailwind with ./pages and ./components with next generators', async () => {
it('should call css-modules-to-tailwind@0.1.9 with ./pages and ./components with next generators', async () => {
await actions.convertCSSModules({
data: {
_: ['next-wp'],
Expand All @@ -22,7 +22,7 @@ describe('convertCSSModules()', () => {
});

expect(vi.mocked(execSync)).toHaveBeenCalledWith(
'npx --prefer-online --yes css-modules-to-tailwind ./pages/**/*.jsx ./pages/*.jsx ./components/*.jsx --force',
'npx --prefer-online --yes css-modules-to-tailwind@0.1.9 ./pages/**/*.jsx ./pages/*.jsx ./components/*.jsx --force',
{
stdio: 'inherit',
encoding: 'utf-8',
Expand All @@ -31,7 +31,7 @@ describe('convertCSSModules()', () => {
);
});

it('should call css-modules-to-tailwind with ./src with gatsby generators', async () => {
it('should call css-modules-to-tailwind@0.1.9 with ./src with gatsby generators', async () => {
await actions.convertCSSModules({
data: {
_: ['gatsby-wp'],
Expand All @@ -43,7 +43,7 @@ describe('convertCSSModules()', () => {
});

expect(vi.mocked(execSync)).toHaveBeenCalledWith(
'npx --prefer-online --yes css-modules-to-tailwind ./src/**/*.jsx --force',
'npx --prefer-online --yes css-modules-to-tailwind@0.1.9 ./src/**/*.jsx ./src/**/*.tsx --force',
{
stdio: 'inherit',
encoding: 'utf-8',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
console.log(`
This is a multiline input!
test input
Testing the diff
`)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const x = 'I am a string';
console.log(x);
Original file line number Diff line number Diff line change
@@ -1 +1 @@
console.log('Hello World')
console.log('this line will be rendered to the template')
11 changes: 7 additions & 4 deletions packages/create-pantheon-decoupled-kit/__tests__/runLint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ describe('runEsLint()', () => {
}));

await actions.runLint({ data });
expect(vi.mocked(execSync)).toHaveBeenCalledWith('npx eslint --fix ', {
stdio: 'inherit',
cwd: outDir('withoutLint'),
});
expect(vi.mocked(execSync)).toHaveBeenCalledWith(
'npx eslint --ext {js,ts} --fix',
{
stdio: 'inherit',
cwd: outDir('withoutLint'),
},
);
expect(whichPMRuns).toHaveBeenCalledOnce();
});

Expand Down
Loading

0 comments on commit 71c8441

Please sign in to comment.