chore: Initial work for migrating tests from Enzyme to React Testing Library (#7090)
#### Details This PR adds a [jscodeshift](https://github.com/facebook/jscodeshift) [codemod](https://github.com/facebookarchive/codemod) to the repo that can be run using `yarn codemod` to migrate test files (set in the paths variable in tools/codeshift.js) that use [Enzyme](https://enzymejs.github.io/enzyme/) to use [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) instead. While this codemod will migrate the low hanging fruit in the tests, it can't cover every scenario perfectly. Tests will need to be incrementally migrated to cut down on PR size. This PR migrates tests in the `src/tests/unit/tests/assessments/` directory to show an example of what that incremental migration will look like. Because our tests make heavy use of Enzyme's `shallow` rendering and React Testing Library does a full mounted render into raw HTML, we needed a solution to keep our snapshots scoped to the actual component we are testing. To address this, this PR includes new mock helper utilities: * `mockReactComponents`: adds a `mockImplementation` for each React Component in the array passed in that renders a React Element instead of the full HTML of that component. * `getMockComponentCall`: retrieves `SomeComponent.mock.calls[callNum]` for SomeComponent and callNum passed in for a mocked React Component * `getMockComponentClassPropsForCall`: replaces `someReactComponent.props()` functionality for a mocked React Component * `expectMockedComponentPropsToMatchSnapshots`: a convenience method to assert that a mocked React Component's props match a snapshot Note: I recommend reviewing this commit by commit in order to understand the flow and changes best. ##### Motivation migrating Enzyme to eventually upgrade React ##### Context After all tests have been migrated and all mentions of Enzyme are gone, the migration can continue: * remove enzyme dependency * upgrade React version * upgrade React Testing Library version, which requires a newer version of React #### Pull request checklist <!-- If a checklist item is not applicable to this change, write "n/a" in the checkbox --> - [n/a] Addresses an existing issue: #0000 - [x] Ran `yarn fastpass` - [x] Added/updated relevant unit test(s) (and ran `yarn test`) - [x] Verified code coverage for the changes made. Check coverage report at: `<rootDir>/test-results/unit/coverage` - [x] PR title *AND* final merge commit title both start with a semantic tag (`fix:`, `chore:`, `feat(feature-name):`, `refactor:`). See `CONTRIBUTING.md`. - [n/a] (UI changes only) Added screenshots/GIFs to description above - [n/a] (UI changes only) Verified usability with NVDA/JAWS
This commit is contained in:
Родитель
620f64608b
Коммит
a250ac0196
|
@ -0,0 +1,129 @@
|
|||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the MIT License.
|
||||
-->
|
||||
|
||||
# Enzyme Migration
|
||||
|
||||
These are cursory notes on the migration to help folks get a sense of the process and some common API differences.
|
||||
|
||||
## Codemod usage
|
||||
|
||||
1. Edit `tools/codeshift.js`:
|
||||
1. change the value of the `paths` variable to include the files you want to migrate
|
||||
1. make sure the `dry: true` line is commented out
|
||||
1. Run `yarn codeshift`
|
||||
1. Run `yarn test -u` to update snapshots
|
||||
1. Commit tests and snapshots that pass and look fairly similar to old snapshots (don’t have a ton of new content due to the full render instead of shallow rendering)
|
||||
1. Make manual changes as needed to finish migrating the remaining tests.
|
||||
> [!NOTE]
|
||||
> Running `yarn test -f -u` and fixing failing tests with each run is helpful
|
||||
|
||||
## Manual code changes
|
||||
|
||||
### Mocking
|
||||
|
||||
A large portion of changes that will need to be manually made are mocking changes. Our tests heavily use Enzyme’s Shallow render functionality. Shallow render does not fully render React components that are used to compose the component being tested but renders them as JSX instead, with their props intact.
|
||||
|
||||
Because React Testing Library does not have equivalent functionality, we need to mock any React Elements that are used to compose the component we are testing to make our snapshot tests meaningful without a bunch of redundant content that is tested elsewhere already. This also solves the issue where props from nested React components are not defined in the test because it wasn’t required for a shallow render but a full render expects it to be there.
|
||||
|
||||
The one caveat to this is if the test requires that element be fully rendered to interact with it (e.g., the test interacts with a button that is rendered through a React element). These tests will likely not use a snapshot.
|
||||
|
||||
#### Mocking components
|
||||
|
||||
Add `jest.mock(‘/path/to/file/with/component’)` under the imports of the file.
|
||||
|
||||
* If there are multiple components that need mocking from the same path, only one `jest.mock` call is needed.
|
||||
* If there are multiple components that need mocking from different paths, there should be one `jest.mock` call per path.
|
||||
|
||||
In the `describe` block, add `mockReactComponents([ComponentName])`.
|
||||
* This array should contain all the components that need to be mocked, though it is often only a single component.
|
||||
|
||||
#### Snapshot parity
|
||||
|
||||
If the test snapshot appears to be missing information because one of the props for the mocked component is rendered as `[Object object]`, an additional snapshot for the props of that component should be added:
|
||||
|
||||
`expectMockedComponentPropsToMatchSnapshot([ComponentName])`
|
||||
|
||||
or
|
||||
|
||||
`expect(getMockComponentClassPropsForCall(ComponentName)).toMatchSnapshot()`
|
||||
|
||||
#### Testing if a component is present (e.g., if the original test code has something like `expect(wrapper.find(ComponentName)).not.toBeNull()`)
|
||||
|
||||
For this case you should mock the component and then use `expect(ComponentName).toBeCalled()`
|
||||
|
||||
|
||||
### API Differences
|
||||
|
||||
The codemod handles all of the straightforward API differences, but many of our tests use custom helpers and have too many interdependent variables for the codemod to be able to correctly migrate it.
|
||||
|
||||
#### `.find(ComponentName)`
|
||||
|
||||
> [!NOTE]
|
||||
> the codemod changes all `.find()` calls, as the vast majority are for Enzyme. However, some number of regular `Array.find()` calls will be mistakenly changed. These can just be changed back.
|
||||
|
||||
> [!NOTE]
|
||||
> the codemod automatically changes `.find()` to be `.querySelector()`, as that is most often the correct API call to migrate to. If the selected variable's value should be an array instead of a standalone element, it should be updated to use `.querySelectorAll()` instead. Additionally, all React Testing Library query calls have “All” variations. For example, `getAllByRole`.
|
||||
|
||||
The treatment of these calls will depend on what operations are performed on the object after this call.
|
||||
|
||||
If the component is some sort of `ButtonComponent` and the old code used `foundComponentVariableName.simulate(‘click’)` after the `.find` call, then this can likely be migrated as `renderResult.getByRole(‘button’)` and then the simulate line can be migrated separately.
|
||||
|
||||
If the component is just being used to verify that the content made it to the page (checking if the found component is `null`), you can instead [mock that component](#mocking-components) and `assert` that the component was called.
|
||||
|
||||
Alternatively, if the test is checking for specific text to be present, something like `renderResult.findByText(‘text content’)` and `assert` that the result is *not* `null`.
|
||||
|
||||
If the test is checking to see if something is *not* there, you will likely need to utilize the `queryBy` API (e.g. `renderResult.queryByRole(‘button’)`), which won’t error if it can’t find a result.
|
||||
|
||||
If the element being searched for is outside of the originally rendered element, you may need to use the `screen.getBy` APIs instead, as this will look for the element in the entire screen, and not just inside a small portion. `screen` is part of `@testing-library/react`.
|
||||
|
||||
You will likely need to look at the composition of the component you are testing to select which [`getBy` or `queryBy` API call](https://testing-library.com/docs/queries/about#priority) you need. The ones most likely to be useful are `getByRole`, `getByLabelText`, and `getByText`
|
||||
|
||||
|
||||
#### `.is(tag)`
|
||||
|
||||
`expect(elementVar.tagName).toBe(tag.toUppercase());`
|
||||
|
||||
|
||||
#### `.simulate`
|
||||
|
||||
Simulating events can be achieved in two ways. The preferred method is using [`userEvent`](https://testing-library.com/docs/user-event/intro), imported from `@testing-library/user-event`. This fires off an actual full fledged event as if an actual user interaction had occurred. The other method is by importing [`fireEvent`](https://testing-library.com/docs/dom-testing-library/api-events#fireevent) from `@testing-library/react`, which still calls `dispatchEvent` but may not fire all events in the same order as an actual user interaction.
|
||||
|
||||
Many of our enzyme tests construct a stubEvent to pass into the simulate call. This stub will not be necessary as a near-real event will be triggered by the new APIs. Any mock functions expecting the stubEvent will have to be modified.
|
||||
|
||||
##### `user-event`
|
||||
|
||||
The codemod will automatically change any `button.simulate(‘click’)` to be `await userEvent.click(renderResult.getByRole(‘button’))` because that works for the majority of cases. You’ll just need to delete the obsolete code previously used to delete the button.
|
||||
|
||||
To type into an input, use `await userEvent.type(elementVariable, value)`. `type` documentation: [https://testing-library.com/docs/user-event/utility#type](https://testing-library.com/docs/user-event/utility#type)
|
||||
|
||||
To press a specific key regardless of element, use `await userEvent.keyboard(keyActions)`. `keyboard` documentation: [https://testing-library.com/docs/user-event/utility#type](https://testing-library.com/docs/user-event/utility#type)
|
||||
|
||||
Check out the [user-event documentation](https://testing-library.com/docs/user-event/intro) if you run into other cases.
|
||||
|
||||
##### `fireEvent`
|
||||
|
||||
`fireEvent` is most useful for when we want to mock an event function like `stopPropagation` and check that it is called because the event can be created and modified before it is passed into `fireEvent`. For example:
|
||||
|
||||
```
|
||||
const event = createEvent.click(link);
|
||||
|
||||
event.stopPropagation = stopPropagationMock;
|
||||
|
||||
fireEvent(link, event);
|
||||
|
||||
expect(stopPropagationMock).toHaveBeenCalledTimes(1);
|
||||
```
|
||||
|
||||
`createEvent` (used in the example above) is also part of `@testing-library/react`.
|
||||
|
||||
#### Manipulating React state
|
||||
|
||||
Directly modifying React state after initial render is not possible in React Testing Library. Instead, you should figure out how that state change is triggered and use [user-event](#user-event) or updated `props` to achieve the same result by interacting with the rendered component.
|
||||
|
||||
To check if an expected React state is produced, test that the UI reflects that state as expected.
|
||||
|
||||
#### Updating React `props` after initial render
|
||||
|
||||
To update `props` after initial render, use the [`rerender`](https://testing-library.com/docs/react-testing-library/api#rerender) function and pass in the new `props`. This function is available on the `renderResult` object produced by the `render` function.
|
|
@ -33,6 +33,7 @@
|
|||
"build:package:axe-config": "grunt generate-axe-config",
|
||||
"change-log": "node ./tools/get-change-log-for-release.js",
|
||||
"clean": "grunt clean:*",
|
||||
"codeshift": "node ./tools/jscodeshift/codeshift.js",
|
||||
"copyright:check": "license-check-and-add check -f copyright-header.config.json",
|
||||
"copyright:fix": "license-check-and-add add -f copyright-header.config.json",
|
||||
"fastpass": "npm-run-all --print-label scss:build --parallel type:check copyright:check lint:check lint:scss format:check null:check && grunt ada-cat",
|
||||
|
@ -77,6 +78,7 @@
|
|||
"@esbuild-plugins/node-resolve": "^0.2.2",
|
||||
"@swc/core": "^1.3.100",
|
||||
"@swc/jest": "^0.2.29",
|
||||
"@testing-library/react": "12.1.2",
|
||||
"@types/chrome": "0.0.254",
|
||||
"@types/enzyme": "^3.10.18",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.9",
|
||||
|
@ -126,6 +128,7 @@
|
|||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jscodeshift": "^0.15.1",
|
||||
"license-check-and-add": "^4.0.5",
|
||||
"mini-css-extract-plugin": "2.7.6",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
@ -153,6 +156,7 @@
|
|||
"dependencies": {
|
||||
"@fluentui/react": "^8.96.1",
|
||||
"@microsoft/applicationinsights-web": "^2.8.15",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"ajv": "^8.12.0",
|
||||
"axe-core": "4.7.2",
|
||||
"classnames": "^2.3.2",
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import * as React from 'react';
|
||||
|
||||
export const expectMockedComponentPropsToMatchSnapshots = (
|
||||
components: any[],
|
||||
snapshotName?: string,
|
||||
) => {
|
||||
components.forEach(component => {
|
||||
const componentSnapshotName =
|
||||
snapshotName ||
|
||||
`${component.displayName || component.name || 'mocked component'} props`;
|
||||
expectMockedComponentPropsToMatchSnapshot(component, componentSnapshotName);
|
||||
});
|
||||
};
|
||||
|
||||
export function getMockComponentCall(obj, callNum = 1) {
|
||||
const calls = (obj as any).mock?.calls || (obj as any).render.mock.calls;
|
||||
return calls.length > callNum - 1 ? calls[callNum - 1] : [];
|
||||
}
|
||||
|
||||
export function getMockComponentClassPropsForCall(obj, callNum = 1) {
|
||||
return getMockComponentCall(obj, callNum)[0];
|
||||
}
|
||||
|
||||
export function mockReactComponents(components: any[]) {
|
||||
components.forEach(component => {
|
||||
mockReactComponent(component);
|
||||
});
|
||||
}
|
||||
|
||||
function mockReactComponent<T extends React.ComponentClass<P>, P = any>(component, elementName?) {
|
||||
if (component !== undefined) {
|
||||
const name =
|
||||
elementName || component.displayName
|
||||
? `mock-${component.displayName}`
|
||||
: `mock-${component.name}`;
|
||||
if (
|
||||
!(component as any).mockImplementation &&
|
||||
!(component as any).render?.mockImplementation
|
||||
) {
|
||||
throw new Error(
|
||||
`${name} is not a mockable component. Please add a jest.mock call for this component before using this component in the test function.`,
|
||||
);
|
||||
}
|
||||
const mockFunction = mockReactElement<P>(name);
|
||||
|
||||
if (component.prototype && component.prototype.isReactComponent) {
|
||||
//mock the class
|
||||
const mockClass = (props: P, context?: any, ...rest: any[]) => ({
|
||||
render: () => mockFunction(props, ...rest),
|
||||
props,
|
||||
context,
|
||||
...rest,
|
||||
});
|
||||
(component as any).mockImplementation(mockClass);
|
||||
} else {
|
||||
//functional component
|
||||
if ((component as any).render?.mockImplementation) {
|
||||
component.render.mockImplementation(mockFunction);
|
||||
}
|
||||
if ((component as any).mockImplementation) {
|
||||
(component as any).mockImplementation(mockFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expectMockedComponentPropsToMatchSnapshot(component: any, snapshotName?: string) {
|
||||
snapshotName !== undefined
|
||||
? expect(getMockComponentClassPropsForCall(component as any)).toMatchSnapshot(snapshotName)
|
||||
: expect(getMockComponentClassPropsForCall(component as any)).toMatchSnapshot();
|
||||
}
|
||||
|
||||
function mockReactElement<P = any>(elementName: string) {
|
||||
const element: React.FC<React.PropsWithChildren<P>> = elementProps => {
|
||||
try {
|
||||
const { children, ...props } = elementProps;
|
||||
return React.createElement(elementName, props, children);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
return React.createElement('pre', {}, e.message);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
return element;
|
||||
}
|
|
@ -1,26 +1,28 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AssistedTestRecordYourResults renders 1`] = `
|
||||
<li>
|
||||
Record your results:
|
||||
<ol>
|
||||
<li>
|
||||
Select
|
||||
<Term>
|
||||
Fail
|
||||
</Term>
|
||||
for any instances that do not meet the requirement.
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select
|
||||
<Term>
|
||||
Pass
|
||||
</Term>
|
||||
. Or, after you have marked all failures, select
|
||||
<Term>
|
||||
Pass unmarked instances.
|
||||
</Term>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<DocumentFragment>
|
||||
<li>
|
||||
Record your results:
|
||||
<ol>
|
||||
<li>
|
||||
Select
|
||||
<strong>
|
||||
Fail
|
||||
</strong>
|
||||
for any instances that do not meet the requirement.
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select
|
||||
<strong>
|
||||
Pass
|
||||
</strong>
|
||||
. Or, after you have marked all failures, select
|
||||
<strong>
|
||||
Pass unmarked instances.
|
||||
</strong>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -1,55 +1,49 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ManualTestRecordYourResultsTest render: isMultipleFailurePossible = false 1`] = `
|
||||
<li>
|
||||
Record your results:
|
||||
<ol>
|
||||
<li>
|
||||
If you find
|
||||
a failure
|
||||
, select
|
||||
|
||||
<Term>
|
||||
Fail
|
||||
</Term>
|
||||
,
|
||||
then add the failure instance
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select
|
||||
<Term>
|
||||
Pass
|
||||
</Term>
|
||||
.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<DocumentFragment>
|
||||
<li>
|
||||
Record your results:
|
||||
<ol>
|
||||
<li>
|
||||
If you find a failure, select
|
||||
<strong>
|
||||
Fail
|
||||
</strong>
|
||||
, then add the failure instance.
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select
|
||||
<strong>
|
||||
Pass
|
||||
</strong>
|
||||
.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`ManualTestRecordYourResultsTest render: isMultipleFailurePossible = true 1`] = `
|
||||
<li>
|
||||
Record your results:
|
||||
<ol>
|
||||
<li>
|
||||
If you find
|
||||
any failures
|
||||
, select
|
||||
|
||||
<Term>
|
||||
Fail
|
||||
</Term>
|
||||
,
|
||||
then add them as failure instances
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select
|
||||
<Term>
|
||||
Pass
|
||||
</Term>
|
||||
.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<DocumentFragment>
|
||||
<li>
|
||||
Record your results:
|
||||
<ol>
|
||||
<li>
|
||||
If you find any failures, select
|
||||
<strong>
|
||||
Fail
|
||||
</strong>
|
||||
, then add them as failure instances.
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select
|
||||
<strong>
|
||||
Pass
|
||||
</strong>
|
||||
.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssistedTestRecordYourResults } from 'assessments/common/assisted-test-record-your-results';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
describe('AssistedTestRecordYourResults', () => {
|
||||
it('renders', () => {
|
||||
const wrapped = shallow(<AssistedTestRecordYourResults />);
|
||||
const renderResult = render(<AssistedTestRecordYourResults />);
|
||||
|
||||
expect(wrapped.getElement()).toMatchSnapshot();
|
||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,83 +1,86 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutomatedChecksVisualizationToggle render with disabled message 1`] = `
|
||||
<div
|
||||
className="visual-helper"
|
||||
>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="visual-helper-text"
|
||||
class="visual-helper"
|
||||
>
|
||||
Visual helper
|
||||
<div
|
||||
class="visual-helper-text"
|
||||
>
|
||||
Visual helper
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<mock-visualizationtoggle
|
||||
checked="false"
|
||||
classname="visual-helper-toggle"
|
||||
data-automation-id="visual-helper-toggle"
|
||||
disabled="true"
|
||||
visualizationname="Visual helper"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="no-matching-elements"
|
||||
>
|
||||
No matching/failing instances were found
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
>
|
||||
<VisualizationToggle
|
||||
checked={false}
|
||||
className="visual-helper-toggle"
|
||||
data-automation-id="visual-helper-toggle"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
visualizationName="Visual helper"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="no-matching-elements"
|
||||
>
|
||||
No matching/failing instances were found
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`AutomatedChecksVisualizationToggle render: toggle disabled when there are no failing instances for automated checks 1`] = `
|
||||
<div
|
||||
className="visual-helper"
|
||||
>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="visual-helper-text"
|
||||
class="visual-helper"
|
||||
>
|
||||
Visual helper
|
||||
<div
|
||||
class="visual-helper-text"
|
||||
>
|
||||
Visual helper
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<mock-visualizationtoggle
|
||||
checked="false"
|
||||
classname="visual-helper-toggle"
|
||||
data-automation-id="visual-helper-toggle"
|
||||
disabled="true"
|
||||
visualizationname="Visual helper"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="no-matching-elements"
|
||||
>
|
||||
No matching/failing instances were found
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
>
|
||||
<VisualizationToggle
|
||||
checked={false}
|
||||
className="visual-helper-toggle"
|
||||
data-automation-id="visual-helper-toggle"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
visualizationName="Visual helper"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className="no-matching-elements"
|
||||
>
|
||||
No matching/failing instances were found
|
||||
</span>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`AutomatedChecksVisualizationToggle render: toggle not disabled 1`] = `
|
||||
<div
|
||||
className="visual-helper"
|
||||
>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="visual-helper-text"
|
||||
class="visual-helper"
|
||||
>
|
||||
Visual helper
|
||||
<div
|
||||
class="visual-helper-text"
|
||||
>
|
||||
Visual helper
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="false"
|
||||
>
|
||||
<mock-visualizationtoggle
|
||||
checked="false"
|
||||
classname="visual-helper-toggle"
|
||||
data-automation-id="visual-helper-toggle"
|
||||
disabled="false"
|
||||
visualizationname="Visual helper"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden={false}
|
||||
>
|
||||
<VisualizationToggle
|
||||
checked={false}
|
||||
className="visual-helper-toggle"
|
||||
data-automation-id="visual-helper-toggle"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
visualizationName="Visual helper"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render, RenderResult } from '@testing-library/react';
|
||||
import { AutomatedChecksVisualizationToggle } from 'assessments/automated-checks/automated-checks-visualization-enabled-toggle';
|
||||
import { AssessmentActionMessageCreator } from 'DetailsView/actions/assessment-action-message-creator';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
getMockComponentClassPropsForCall,
|
||||
mockReactComponents,
|
||||
} from 'tests/unit/mock-helpers/mock-module-helpers';
|
||||
import { IMock, Mock } from 'typemoq';
|
||||
import {
|
||||
VisualizationToggle,
|
||||
|
@ -13,7 +17,9 @@ import { visualHelperText } from '../../../../../DetailsView/components/base-vis
|
|||
import { VisualHelperToggleConfigBuilder } from '../../../common/visual-helper-toggle-config-builder';
|
||||
import { VisualizationTogglePropsBuilder } from '../../../common/visualization-toggle-props-builder';
|
||||
|
||||
jest.mock('common/components/visualization-toggle');
|
||||
describe('AutomatedChecksVisualizationToggle', () => {
|
||||
mockReactComponents([VisualizationToggle]);
|
||||
const assessmentActionMessageCreatorMock: IMock<AssessmentActionMessageCreator> = Mock.ofType(
|
||||
AssessmentActionMessageCreator,
|
||||
);
|
||||
|
@ -26,26 +32,24 @@ describe('AutomatedChecksVisualizationToggle', () => {
|
|||
.withEmptyFilteredMap()
|
||||
.build();
|
||||
|
||||
const wrapper = shallow(<AutomatedChecksVisualizationToggle {...props} />);
|
||||
const renderResult = render(<AutomatedChecksVisualizationToggle {...props} />);
|
||||
|
||||
const visualHelperClass = 'visual-helper';
|
||||
const toggleDiv = wrapper.find(`.${visualHelperClass}`);
|
||||
const textDiv = toggleDiv.find(`.${visualHelperClass}-text`);
|
||||
const toggleDiv = renderResult.container.querySelector(`.${visualHelperClass}`);
|
||||
const textDiv = toggleDiv.querySelector(`.${visualHelperClass}-text`);
|
||||
const noMatchesWarningClass = 'no-matching-elements';
|
||||
|
||||
expect(toggleDiv.exists()).toBe(true);
|
||||
expect(textDiv.exists()).toBe(true);
|
||||
expect(wrapper.find(`.${noMatchesWarningClass}`).exists()).toBe(true);
|
||||
|
||||
const toggle = wrapper.find(VisualizationToggle);
|
||||
expect(toggleDiv).not.toBeNull();
|
||||
expect(textDiv).not.toBeNull();
|
||||
expect(renderResult.container.querySelector(`.${noMatchesWarningClass}`)).not.toBeNull();
|
||||
|
||||
const expectedToggleProps = getDefaultVisualizationTogglePropsBuilder()
|
||||
.with('checked', false)
|
||||
.with('disabled', true)
|
||||
.build();
|
||||
|
||||
assertVisualizationToggle(expectedToggleProps, toggle);
|
||||
assertSnapshotMatch(wrapper);
|
||||
assertVisualizationToggle(expectedToggleProps);
|
||||
assertSnapshotMatch(renderResult);
|
||||
});
|
||||
|
||||
it('render: toggle not disabled', () => {
|
||||
|
@ -56,27 +60,26 @@ describe('AutomatedChecksVisualizationToggle', () => {
|
|||
.withNonEmptyFilteredMap()
|
||||
.build();
|
||||
|
||||
const wrapper = shallow(<AutomatedChecksVisualizationToggle {...props} />);
|
||||
const renderResult = render(<AutomatedChecksVisualizationToggle {...props} />);
|
||||
|
||||
const visualHelperClass = 'visual-helper';
|
||||
const toggleDiv = wrapper.find(`.${visualHelperClass}`);
|
||||
const toggleDiv = renderResult.container.querySelector(`.${visualHelperClass}`);
|
||||
|
||||
expect(toggleDiv.exists()).toBe(true);
|
||||
expect(toggleDiv).not.toBeNull();
|
||||
|
||||
const textDiv = toggleDiv.find(`.${visualHelperClass}-text`);
|
||||
const textDiv = toggleDiv.querySelector(`.${visualHelperClass}-text`);
|
||||
|
||||
expect(textDiv.exists()).toBe(true);
|
||||
expect(textDiv).not.toBeNull();
|
||||
|
||||
expect(wrapper.find('strong').exists()).toBeFalsy();
|
||||
const toggle = wrapper.find(VisualizationToggle);
|
||||
expect(renderResult.container.querySelector('strong')).toBeNull();
|
||||
|
||||
const expectedToggleProps = getDefaultVisualizationTogglePropsBuilder()
|
||||
.with('checked', false)
|
||||
.with('disabled', false)
|
||||
.build();
|
||||
|
||||
assertVisualizationToggle(expectedToggleProps, toggle);
|
||||
assertSnapshotMatch(wrapper);
|
||||
assertVisualizationToggle(expectedToggleProps);
|
||||
assertSnapshotMatch(renderResult);
|
||||
});
|
||||
|
||||
it('render: toggle disabled when there are no failing instances for automated checks', () => {
|
||||
|
@ -87,44 +90,39 @@ describe('AutomatedChecksVisualizationToggle', () => {
|
|||
.withPassingFilteredMap()
|
||||
.build();
|
||||
|
||||
const wrapper = shallow(<AutomatedChecksVisualizationToggle {...props} />);
|
||||
const renderResult = render(<AutomatedChecksVisualizationToggle {...props} />);
|
||||
const visualHelperClass = 'visual-helper';
|
||||
const toggleDiv = wrapper.find(`.${visualHelperClass}`);
|
||||
const toggleDiv = renderResult.container.querySelector(`.${visualHelperClass}`);
|
||||
|
||||
expect(toggleDiv.exists()).toBe(true);
|
||||
expect(toggleDiv).not.toBeNull();
|
||||
|
||||
const textDiv = toggleDiv.find(`.${visualHelperClass}-text`);
|
||||
const textDiv = toggleDiv.querySelector(`.${visualHelperClass}-text`);
|
||||
|
||||
expect(textDiv.exists()).toBe(true);
|
||||
expect(textDiv).not.toBeNull();
|
||||
|
||||
const noMatchesWarningClass = 'no-matching-elements';
|
||||
|
||||
const toggle = wrapper.find(VisualizationToggle);
|
||||
|
||||
expect(wrapper.find(`.${noMatchesWarningClass}`).exists()).toBe(true);
|
||||
expect(renderResult.container.querySelector(`.${noMatchesWarningClass}`)).not.toBeNull();
|
||||
|
||||
const expectedToggleProps = getDefaultVisualizationTogglePropsBuilder()
|
||||
.with('checked', false)
|
||||
.with('disabled', true)
|
||||
.build();
|
||||
|
||||
assertVisualizationToggle(expectedToggleProps, toggle);
|
||||
assertSnapshotMatch(wrapper);
|
||||
assertVisualizationToggle(expectedToggleProps);
|
||||
assertSnapshotMatch(renderResult);
|
||||
});
|
||||
|
||||
function assertVisualizationToggle(
|
||||
expectedProps: VisualizationToggleProps,
|
||||
visualizationToggle: ShallowWrapper<VisualizationToggleProps>,
|
||||
): void {
|
||||
expect(visualizationToggle.exists()).toBe(true);
|
||||
function assertVisualizationToggle(expectedProps: VisualizationToggleProps): void {
|
||||
expect(VisualizationToggle).toBeCalled();
|
||||
|
||||
const actualProps = visualizationToggle.props();
|
||||
const actualProps = getMockComponentClassPropsForCall(VisualizationToggle);
|
||||
|
||||
expect(actualProps).toMatchObject(expectedProps);
|
||||
}
|
||||
|
||||
function assertSnapshotMatch(wrapper: ShallowWrapper): void {
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
function assertSnapshotMatch(wrapper: RenderResult): void {
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
}
|
||||
|
||||
function getDefaultVisualizationTogglePropsBuilder(): VisualizationTogglePropsBuilder {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`InstructionsAndLabelsNote renders 1`] = `
|
||||
<Emphasis>
|
||||
Note: Both WCAG 2.0 and 2.1 require a widget's visible label and instructions (if present) to be programmatically determinable. WCAG 2.1 also requires a widget's visible label and instructions (if present) to be included in its accessible name and description.
|
||||
</Emphasis>
|
||||
<DocumentFragment>
|
||||
<em>
|
||||
Note: Both WCAG 2.0 and 2.1 require a widget's visible label and instructions (if present) to be programmatically determinable. WCAG 2.1 also requires a widget's visible label and instructions (if present) to be included in its accessible name and description.
|
||||
</em>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -1,138 +1,228 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render with a combination of expanded and not expanded objects 1`] = `
|
||||
"<div className="property-bag-container">
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display e:
|
||||
</span>
|
||||
<div className="expanded-property-div">
|
||||
<span className="display-name">
|
||||
key1:
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display e:
|
||||
</span>
|
||||
value1
|
||||
<div
|
||||
class="expanded-property-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
key1:
|
||||
</span>
|
||||
value1
|
||||
</div>
|
||||
<div
|
||||
class="expanded-property-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
key2:
|
||||
</span>
|
||||
value2
|
||||
</div>
|
||||
</div>
|
||||
<div className="expanded-property-div">
|
||||
<span className="display-name">
|
||||
key2:
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display b:
|
||||
</span>
|
||||
value2
|
||||
-1
|
||||
</div>
|
||||
</div>
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display b:
|
||||
</span>
|
||||
-1
|
||||
</div>
|
||||
</div>"
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render with expanded object 1`] = `
|
||||
"<div className="property-bag-container">
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display e:
|
||||
</span>
|
||||
<div className="expanded-property-div">
|
||||
<span className="display-name">
|
||||
key1:
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display e:
|
||||
</span>
|
||||
value1
|
||||
</div>
|
||||
<div className="expanded-property-div">
|
||||
<span className="display-name">
|
||||
key2:
|
||||
</span>
|
||||
value2
|
||||
<div
|
||||
class="expanded-property-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
key1:
|
||||
</span>
|
||||
value1
|
||||
</div>
|
||||
<div
|
||||
class="expanded-property-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
key2:
|
||||
</span>
|
||||
value2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>"
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render with expanded object where some have null values 1`] = `
|
||||
"<div className="property-bag-container">
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display null values:
|
||||
</span>
|
||||
<div className="expanded-property-div">
|
||||
<span className="display-name">
|
||||
key1:
|
||||
</span>
|
||||
value1
|
||||
</div>
|
||||
<div className="expanded-property-div">
|
||||
<span className="display-name">
|
||||
key2
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display null values:
|
||||
</span>
|
||||
<div
|
||||
class="expanded-property-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
key1:
|
||||
</span>
|
||||
value1
|
||||
</div>
|
||||
<div
|
||||
class="expanded-property-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
key2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>"
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render, default value 1`] = `
|
||||
"<div className="property-bag-container">
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display a:
|
||||
</span>
|
||||
is a default value
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display a:
|
||||
</span>
|
||||
is a default value
|
||||
</div>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display b:
|
||||
</span>
|
||||
-1
|
||||
</div>
|
||||
</div>
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display b:
|
||||
</span>
|
||||
-1
|
||||
</div>
|
||||
</div>"
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render, empty object 1`] = `
|
||||
"<div className="property-bag-container">
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display f:
|
||||
</span>
|
||||
default
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display f:
|
||||
</span>
|
||||
default
|
||||
</div>
|
||||
</div>
|
||||
</div>"
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render, one property from the bag 1`] = `
|
||||
<div
|
||||
className="property-bag-container"
|
||||
>
|
||||
<DocumentFragment>
|
||||
<div
|
||||
className="property-bag-div"
|
||||
class="property-bag-container"
|
||||
>
|
||||
<span
|
||||
className="display-name"
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
display name:
|
||||
</span>
|
||||
<React.Fragment>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display name:
|
||||
</span>
|
||||
string value
|
||||
</React.Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render, property is null, no default value 1`] = `"<div className="property-bag-container" />"`;
|
||||
exports[`PropertyBagColumnRendererTest render, property is null, no default value 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PropertyBagColumnRendererTest render, several properties from the bag 1`] = `
|
||||
"<div className="property-bag-container">
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display a:
|
||||
</span>
|
||||
string value
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="property-bag-container"
|
||||
>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display a:
|
||||
</span>
|
||||
string value
|
||||
</div>
|
||||
<div
|
||||
class="property-bag-div"
|
||||
>
|
||||
<span
|
||||
class="display-name"
|
||||
>
|
||||
display b:
|
||||
</span>
|
||||
-1
|
||||
</div>
|
||||
</div>
|
||||
<div className="property-bag-div">
|
||||
<span className="display-name">
|
||||
display b:
|
||||
</span>
|
||||
-1
|
||||
</div>
|
||||
</div>"
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { shallow } from 'enzyme';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { InstructionsAndLabelsNotes } from '../../../../../assessments/common/instructions-and-labels-note';
|
||||
|
||||
describe('InstructionsAndLabelsNote', () => {
|
||||
it('renders', () => {
|
||||
const wrapped = shallow(<InstructionsAndLabelsNotes />);
|
||||
const renderResult = render(<InstructionsAndLabelsNotes />);
|
||||
|
||||
expect(wrapped.getElement()).toMatchSnapshot();
|
||||
expect(renderResult.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render, within } from '@testing-library/react';
|
||||
import { PropertyBagColumnRendererFactory } from 'assessments/common/property-bag-column-renderer-factory';
|
||||
import { InstanceTableRow } from 'assessments/types/instance-table-data';
|
||||
import { PropertyBagColumnRendererConfig } from 'common/types/property-bag/property-bag-column-renderer-config';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ColumnValueBag } from '../../../../../common/types/property-bag/column-value-bag';
|
||||
|
@ -40,22 +40,13 @@ describe('PropertyBagColumnRendererFactoryTest', () => {
|
|||
|
||||
const renderer = () => result(item);
|
||||
|
||||
const wrapper = shallow(<RendererWrapper render={renderer} />);
|
||||
|
||||
const div = wrapper.find('.property-bag-container');
|
||||
|
||||
expect(div.exists()).toBeTruthy();
|
||||
|
||||
const outterSpan = div.childAt(0);
|
||||
|
||||
expect(outterSpan.exists()).toBeTruthy();
|
||||
expect(outterSpan.hasClass('property-bag-div')).toBeTruthy();
|
||||
expect(outterSpan.children().length).toBe(2);
|
||||
|
||||
const displayNameSpan = outterSpan.childAt(0);
|
||||
|
||||
expect(displayNameSpan.exists()).toBeTruthy();
|
||||
expect(displayNameSpan.hasClass('display-name')).toBeTruthy();
|
||||
expect(`${configs[0].displayName}: `).toBe(displayNameSpan.text());
|
||||
const renderResult = render(<RendererWrapper render={renderer} />);
|
||||
const div = renderResult.container.querySelector('.property-bag-container');
|
||||
expect(div).not.toBeNull();
|
||||
const propertyBagValue = within(div as HTMLElement).getByText(item.instance.propertyBag.a);
|
||||
const displayNameSpan = within(div as HTMLElement).getByText(`${configs[0].displayName}:`);
|
||||
expect(propertyBagValue).not.toBeNull();
|
||||
expect(displayNameSpan).not.toBeNull();
|
||||
expect(displayNameSpan.classList.contains('display-name')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render, RenderResult } from '@testing-library/react';
|
||||
import { propertyBagColumnRenderer } from 'assessments/common/property-bag-column-renderer';
|
||||
import { InstanceTableRow } from 'assessments/types/instance-table-data';
|
||||
import { PropertyBagColumnRendererConfig } from 'common/types/property-bag/property-bag-column-renderer-config';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ColumnValueBag } from '../../../../../common/types/property-bag/column-value-bag';
|
||||
|
@ -34,7 +34,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render, several properties from the bag', () => {
|
||||
|
@ -48,7 +48,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render with expanded object', () => {
|
||||
|
@ -61,7 +61,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render with expanded object where some have null values', () => {
|
||||
|
@ -74,7 +74,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render, empty object', () => {
|
||||
|
@ -87,7 +87,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render, default value', () => {
|
||||
|
@ -102,7 +102,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render, property is null, no default value', () => {
|
||||
|
@ -119,7 +119,7 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render with a combination of expanded and not expanded objects', () => {
|
||||
|
@ -133,14 +133,14 @@ describe('PropertyBagColumnRendererTest', () => {
|
|||
|
||||
const wrapper = createWrapper(item, configs);
|
||||
|
||||
expect(wrapper.debug()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
function createWrapper(item, configs): ShallowWrapper {
|
||||
function createWrapper(item, configs): RenderResult {
|
||||
const renderer = () => propertyBagColumnRenderer(item, configs);
|
||||
|
||||
return shallow(<RendererWrapper render={renderer} />);
|
||||
return render(<RendererWrapper render={renderer} />);
|
||||
}
|
||||
|
||||
function getPropertyBag(): TestPropertyBag {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render } from '@testing-library/react';
|
||||
import { CustomWidgetsColumnRendererFactory } from 'assessments/custom-widgets/custom-widgets-column-renderer-factory';
|
||||
import { InstanceTableRow } from 'assessments/types/instance-table-data';
|
||||
import { PropertyBagColumnRendererConfig } from 'common/types/property-bag/property-bag-column-renderer-config';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ColumnValueBag } from '../../../../../common/types/property-bag/column-value-bag';
|
||||
|
@ -44,32 +44,32 @@ describe('CustomWidgetsColumnRendererFactory', () => {
|
|||
it('getWithLink should render link', () => {
|
||||
const result = CustomWidgetsColumnRendererFactory.getWithLink(configs);
|
||||
|
||||
checkPropertyBagAndTag(result, 'NewTabLink');
|
||||
checkPropertyBagAndTag(result, 'A');
|
||||
});
|
||||
|
||||
it('getWithoutLink should render text', () => {
|
||||
const result = CustomWidgetsColumnRendererFactory.getWithoutLink(configs);
|
||||
|
||||
checkPropertyBagAndTag(result, 'span');
|
||||
checkPropertyBagAndTag(result, 'SPAN');
|
||||
});
|
||||
|
||||
function checkPropertyBagAndTag(result: Function, tag: string): void {
|
||||
const renderer = () => result(item);
|
||||
|
||||
const wrapper = shallow(<RendererWrapper render={renderer} />);
|
||||
const renderResult = render(<RendererWrapper render={renderer} />);
|
||||
|
||||
const div = wrapper.find('.property-bag-container');
|
||||
expect(div.exists()).toBeTruthy();
|
||||
expect(div.children()).toHaveLength(2);
|
||||
const div = renderResult.container.querySelector('.property-bag-container');
|
||||
expect(div).not.toBeNull();
|
||||
expect(div.children).toHaveLength(2);
|
||||
|
||||
const designPatternSpan = div.childAt(1);
|
||||
const designPatternSpan = div.children[1];
|
||||
expect(designPatternSpan).not.toBeUndefined();
|
||||
expect(designPatternSpan.hasClass('property-bag-div')).toBeTruthy();
|
||||
expect(designPatternSpan.children()).toHaveLength(2);
|
||||
expect(designPatternSpan.classList.contains('property-bag-div')).toBeTruthy();
|
||||
expect(designPatternSpan.children).toHaveLength(2);
|
||||
|
||||
const contentElement = designPatternSpan.childAt(1).childAt(0);
|
||||
expect(contentElement.exists()).toBeTruthy();
|
||||
expect(contentElement.is(tag)).toBeTruthy();
|
||||
const contentElement = designPatternSpan.children[1].children[0];
|
||||
expect(contentElement).not.toBeNull();
|
||||
expect(contentElement.tagName).toBe(tag);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render } from '@testing-library/react';
|
||||
import { customWidgetsColumnRenderer } from 'assessments/custom-widgets/custom-widgets-column-renderer';
|
||||
import { InstanceTableRow } from 'assessments/types/instance-table-data';
|
||||
import { PropertyBagColumnRendererConfig } from 'common/types/property-bag/property-bag-column-renderer-config';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { ColumnValueBag } from '../../../../../common/types/property-bag/column-value-bag';
|
||||
|
@ -43,17 +43,17 @@ describe('CustomWidgetsColumnRenderer', () => {
|
|||
it('should render unrelated properties', () => {
|
||||
const renderer = () => customWidgetsColumnRenderer(item, configs, true);
|
||||
|
||||
const wrapper = shallow(<RendererWrapper render={renderer} />);
|
||||
const renderResult = render(<RendererWrapper render={renderer} />);
|
||||
|
||||
const div = wrapper.find('.property-bag-container');
|
||||
expect(div.exists()).toBeTruthy();
|
||||
expect(div.children()).toHaveLength(1);
|
||||
const div = renderResult.container.querySelector('.property-bag-container');
|
||||
expect(div).not.toBeNull();
|
||||
expect(div.children).toHaveLength(1);
|
||||
|
||||
const designPatternSpan = div.childAt(0);
|
||||
const designPatternSpan = div.children[0];
|
||||
expect(designPatternSpan).not.toBeUndefined();
|
||||
expect(designPatternSpan.hasClass('property-bag-div')).toBeTruthy();
|
||||
expect(designPatternSpan.children()).toHaveLength(2);
|
||||
expect(designPatternSpan.text()).toEqual(
|
||||
expect(designPatternSpan.classList.contains('property-bag-div')).toBeTruthy();
|
||||
expect(designPatternSpan.children).toHaveLength(1);
|
||||
expect(designPatternSpan.textContent).toEqual(
|
||||
`${configs[0].displayName}: ${item.instance.propertyBag.a}`,
|
||||
);
|
||||
});
|
||||
|
@ -81,21 +81,21 @@ describe('CustomWidgetsColumnRenderer', () => {
|
|||
|
||||
const renderer = () => customWidgetsColumnRenderer(item, configs, true);
|
||||
|
||||
const wrapper = shallow(<RendererWrapper render={renderer} />);
|
||||
const renderResult = render(<RendererWrapper render={renderer} />);
|
||||
|
||||
const div = wrapper.find('.property-bag-container');
|
||||
expect(div.exists()).toBeTruthy();
|
||||
expect(div.children()).toHaveLength(2);
|
||||
const div = renderResult.container.querySelector('.property-bag-container');
|
||||
expect(div).not.toBeNull();
|
||||
expect(div.children).toHaveLength(2);
|
||||
|
||||
const designPatternSpan = div.childAt(1);
|
||||
const designPatternSpan = div.children[1];
|
||||
expect(designPatternSpan).not.toBeUndefined();
|
||||
expect(designPatternSpan.hasClass('property-bag-div')).toBeTruthy();
|
||||
expect(designPatternSpan.classList.contains('property-bag-div')).toBeTruthy();
|
||||
|
||||
const links = designPatternSpan.find('NewTabLink');
|
||||
const links = designPatternSpan.querySelectorAll('a');
|
||||
expect(links).toHaveLength(4);
|
||||
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
checkLink(links.get(i), expectedValues[i].designPattern, expectedValues[i].URL);
|
||||
checkLink(links.item(i), expectedValues[i].designPattern, expectedValues[i].URL);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -114,22 +114,22 @@ describe('CustomWidgetsColumnRenderer', () => {
|
|||
|
||||
const renderer = () => customWidgetsColumnRenderer(item, configs, false);
|
||||
|
||||
const wrapper = shallow(<RendererWrapper render={renderer} />);
|
||||
const renderResult = render(<RendererWrapper render={renderer} />);
|
||||
|
||||
const div = wrapper.find('.property-bag-container');
|
||||
expect(div.exists()).toBeTruthy();
|
||||
expect(div.children()).toHaveLength(2);
|
||||
const spans = div.find('.expanded-property-div > span');
|
||||
const div = renderResult.container.querySelector('.property-bag-container');
|
||||
expect(div).not.toBeNull();
|
||||
expect(div.children).toHaveLength(2);
|
||||
const spans = div.querySelectorAll('.expanded-property-div > span');
|
||||
expect(spans).toHaveLength(2);
|
||||
|
||||
for (let i = 0; i < spans.length; i++) {
|
||||
expect(spans.get(i).props.children).toEqual(expectedValues[i].designPattern);
|
||||
expect(spans.item(i).textContent).toEqual(expectedValues[i].designPattern);
|
||||
}
|
||||
});
|
||||
|
||||
function checkLink(link: React.ReactElement<any>, name: string, url: string): void {
|
||||
expect(link.props.href).toEqual(url);
|
||||
expect(link.props.children).toEqual(name);
|
||||
function checkLink(link: Element, name: string, url: string): void {
|
||||
expect(link.getAttribute('href')).toEqual(url);
|
||||
expect(link.textContent).toEqual(name);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
import { render } from '@testing-library/react';
|
||||
import { ManualTestRecordYourResults } from 'assessments/common/manual-test-record-your-results';
|
||||
import * as Enzyme from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
describe('ManualTestRecordYourResultsTest', () => {
|
||||
|
@ -9,8 +9,8 @@ describe('ManualTestRecordYourResultsTest', () => {
|
|||
|
||||
it.each(multiple)('render: isMultipleFailurePossible = %p', isMultipleFailurePossible => {
|
||||
const props = { isMultipleFailurePossible };
|
||||
const wrapper = Enzyme.shallow(<ManualTestRecordYourResults {...props} />);
|
||||
const wrapper = render(<ManualTestRecordYourResults {...props} />);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
const path = require('node:path');
|
||||
const { run: jscodeshift } = require('jscodeshift/src/Runner');
|
||||
|
||||
const transformPath = path.resolve('./tools/jscodeshift/convert-enzyme-to-rtl.js');
|
||||
const paths = ['./src/tests/'];
|
||||
const options = {
|
||||
// dry: true,
|
||||
// print: true,
|
||||
verbose: 2,
|
||||
extensions: 'tsx',
|
||||
parser: 'tsx',
|
||||
// ...
|
||||
};
|
||||
|
||||
jscodeshift(transformPath, paths, options).then(res => {
|
||||
console.log(res);
|
||||
});
|
|
@ -0,0 +1,311 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
const rtlSourceName = '@testing-library/react';
|
||||
const userEventSourceName = '@testing-library/user-event';
|
||||
const placeholderVariableName = 'renderResult';
|
||||
|
||||
function addSourceImport(j, root, source, name) {
|
||||
const sourceImport = root.find(j.ImportDeclaration, {
|
||||
source: { value: source },
|
||||
});
|
||||
const importSpecifier = j.importSpecifier(j.identifier(name));
|
||||
const sourceLiteral = j.stringLiteral(source);
|
||||
if (sourceImport.length === 0) {
|
||||
const importDeclaration = j.importDeclaration([importSpecifier], sourceLiteral);
|
||||
root.get().node.program.body.unshift(importDeclaration);
|
||||
} else {
|
||||
const namedImport = sourceImport.find(j.ImportSpecifier, { imported: { name } });
|
||||
if (namedImport.length === 0) {
|
||||
const existingImportSpecifiers = sourceImport.get().value.specifiers;
|
||||
sourceImport.replaceWith(
|
||||
j.importDeclaration([...existingImportSpecifiers, importSpecifier], sourceLiteral),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeImportSpecifier(j, root, importDeclaration, name) {
|
||||
const numberOfImportsFromSource = importDeclaration.get().value?.specifiers?.length;
|
||||
if (numberOfImportsFromSource) {
|
||||
if (numberOfImportsFromSource === 1) {
|
||||
importDeclaration.remove();
|
||||
} else {
|
||||
const namedImportSpecifier = root.find(j.ImportSpecifier, {
|
||||
imported: { name },
|
||||
});
|
||||
namedImportSpecifier.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCallExpressionByCalleeName(j, root, name) {
|
||||
return root.find(j.CallExpression, { callee: { name } });
|
||||
}
|
||||
|
||||
function getVariableDeclaratorByCalleeName(j, root, name) {
|
||||
return root.find(j.VariableDeclarator, { init: { callee: { name } } });
|
||||
}
|
||||
|
||||
const renameVariableDeclarator = (j, path, newIdentifier) => {
|
||||
j(path).renameTo(newIdentifier);
|
||||
};
|
||||
|
||||
const replaceCallExpressionIdentifier = (j, root, identifier, newIdentifier, args) => {
|
||||
getCallExpressionByCalleeName(j, root, identifier).forEach(ce => {
|
||||
j(ce).replaceWith(
|
||||
j.callExpression(
|
||||
j.identifier(newIdentifier),
|
||||
args !== undefined ? args : j(ce).get().value.arguments,
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const appendIfNecessary = (name, newProperty, appendage) => {
|
||||
const appendageDefault = 'container.';
|
||||
return `${
|
||||
name === placeholderVariableName
|
||||
? typeof appendage === 'string'
|
||||
? appendage
|
||||
: appendageDefault
|
||||
: ''
|
||||
}${newProperty}`;
|
||||
};
|
||||
|
||||
const replaceCallExpressionWithMemberExpression = (
|
||||
j,
|
||||
path,
|
||||
newProperty,
|
||||
appendage = '.container',
|
||||
) => {
|
||||
const callee = path.get().value.callee || path.get().value;
|
||||
if (callee?.object?.name === placeholderVariableName) {
|
||||
callee.object.name += appendage;
|
||||
}
|
||||
path.replaceWith(j.memberExpression(callee.object, newProperty));
|
||||
};
|
||||
|
||||
const replaceCallExpressionProperty = (j, callExpression, newProperty, args, appendage) => {
|
||||
const callee = j(callExpression).get().value.callee;
|
||||
if (appendage !== undefined) {
|
||||
newProperty = appendIfNecessary(callee.object.name, newProperty, appendage);
|
||||
}
|
||||
callee.property = j.identifier(newProperty);
|
||||
j(callExpression).replaceWith(
|
||||
j.callExpression(
|
||||
callee,
|
||||
args !== undefined ? args : j(callExpression).get().value.arguments,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const getCallExpressionByProperty = (j, root, name) => {
|
||||
return root.find(j.CallExpression, { callee: { property: { name } } });
|
||||
};
|
||||
|
||||
function transformer(fileInfo, api) {
|
||||
const j = api.jscodeshift;
|
||||
const root = j(fileInfo.source);
|
||||
// find the enzyme import
|
||||
const enzymeImportDeclaration = root.find(j.ImportDeclaration, { source: { value: 'enzyme' } });
|
||||
if (enzymeImportDeclaration.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getFirstNode = () => root.find(j.Program).get('body', 0).node;
|
||||
|
||||
// save the comments attached to the first node then remove them
|
||||
const firstNode = getFirstNode();
|
||||
const { comments } = firstNode;
|
||||
firstNode.comments = null;
|
||||
|
||||
// add render import
|
||||
addSourceImport(j, root, rtlSourceName, 'render');
|
||||
|
||||
// remove shallow import
|
||||
removeImportSpecifier(j, root, enzymeImportDeclaration, 'shallow');
|
||||
getVariableDeclaratorByCalleeName(j, root, 'shallow').forEach(path => {
|
||||
renameVariableDeclarator(j, path, placeholderVariableName);
|
||||
});
|
||||
replaceCallExpressionIdentifier(j, root, 'shallow', 'render');
|
||||
|
||||
// remove mount
|
||||
removeImportSpecifier(j, root, enzymeImportDeclaration, 'mount');
|
||||
getVariableDeclaratorByCalleeName(j, root, 'mount').forEach(path => {
|
||||
renameVariableDeclarator(j, path, placeholderVariableName);
|
||||
});
|
||||
replaceCallExpressionIdentifier(j, root, 'mount', 'render');
|
||||
|
||||
let shallowTypeRef = root.find(j.TSTypeReference, { typeName: { name: 'ShallowWrapper' } });
|
||||
if (shallowTypeRef.length > 0) {
|
||||
addSourceImport(j, root, rtlSourceName, 'RenderResult');
|
||||
shallowTypeRef.replaceWith(j.tsTypeReference(j.identifier('RenderResult')));
|
||||
|
||||
removeImportSpecifier(j, root, enzymeImportDeclaration, 'ShallowWrapper');
|
||||
}
|
||||
|
||||
// expect(renderResult.getElement()).toBeNull() --> expect(renderResult.container.firstChild).toBeNull();
|
||||
getCallExpressionByProperty(j, root, 'toBeNull').forEach(path => {
|
||||
getCallExpressionByCalleeName(j, j(path), 'expect').forEach(p => {
|
||||
getCallExpressionByProperty(j, j(p), 'getElement').forEach(ge => {
|
||||
replaceCallExpressionWithMemberExpression(j, j(ge), j.identifier('firstChild'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// expect(renderResult.getElement()).not.toBeNull() --> expect(renderResult.container.firstChild).not.toBeNull();
|
||||
getCallExpressionByProperty(j, root, 'not.toBeNull').forEach(path => {
|
||||
getCallExpressionByCalleeName(j, j(path), 'expect').forEach(p => {
|
||||
getCallExpressionByProperty(j, j(p), 'getElement').forEach(ge => {
|
||||
replaceCallExpressionWithMemberExpression(j, j(ge), j.identifier('firstChild'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// wrapper.getElement() --> wrapper.asFragment()
|
||||
getCallExpressionByProperty(j, root, 'getElement').forEach(ce => {
|
||||
replaceCallExpressionProperty(j, ce, 'asFragment');
|
||||
});
|
||||
|
||||
// renderResult.getDOMNode() --> renderResult.asFragment()
|
||||
getCallExpressionByProperty(j, root, 'getDOMNode').forEach(ce => {
|
||||
replaceCallExpressionProperty(j, ce, 'asFragment', undefined, '.container');
|
||||
});
|
||||
|
||||
// replace element.debug() with element.asFragment()
|
||||
getCallExpressionByProperty(j, root, 'debug').forEach(ce => {
|
||||
replaceCallExpressionProperty(j, ce, 'asFragment');
|
||||
});
|
||||
|
||||
// find('.selector') --> querySelector('.selector')
|
||||
getCallExpressionByProperty(j, root, 'find').forEach(path => {
|
||||
const args = j(path).get().value.arguments;
|
||||
replaceCallExpressionProperty(j, path, 'querySelector', args, true);
|
||||
});
|
||||
|
||||
// expect(something.exists()).toBe(true) --> expect(something).not.toBeNull();
|
||||
getCallExpressionByProperty(j, root, 'toBe').forEach(path => {
|
||||
getCallExpressionByCalleeName(j, j(path), 'expect').forEach(p => {
|
||||
getCallExpressionByProperty(j, j(p), 'exists').forEach(ec => {
|
||||
j(ec).replaceWith(j(ec).get().value.callee.object);
|
||||
|
||||
const identifier =
|
||||
j(path).get().value.arguments[0].value === true ? 'not.toBeNull' : 'toBeNull';
|
||||
replaceCallExpressionProperty(j, path, identifier, [], 'container.firstChild');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// expect(something.exists()).toBeTruthy() --> expect(something).not.toBeNull();
|
||||
getCallExpressionByProperty(j, root, 'toBeTruthy').forEach(path => {
|
||||
getCallExpressionByCalleeName(j, j(path), 'expect').forEach(p => {
|
||||
const existsCall = getCallExpressionByProperty(j, j(p), 'exists');
|
||||
if (existsCall.length > 0) {
|
||||
existsCall.replaceWith(existsCall.get().value.callee.object);
|
||||
replaceCallExpressionProperty(j, path, 'not.toBeNull');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// element.children() --> element.children
|
||||
getCallExpressionByProperty(j, root, 'children').forEach(path => {
|
||||
replaceCallExpressionWithMemberExpression(j, j(path), j.identifier('children'));
|
||||
});
|
||||
|
||||
// element.text() --> element.textContent
|
||||
getCallExpressionByProperty(j, root, 'text').forEach(path => {
|
||||
replaceCallExpressionWithMemberExpression(j, j(path), j.identifier('textContent'));
|
||||
});
|
||||
|
||||
const replaceChildAtCall = path => {
|
||||
const property =
|
||||
j(path).get().value.arguments?.length > 0
|
||||
? j.identifier(`children[${j(path).get().value.arguments[0].value}]`)
|
||||
: j(path).get().value.property;
|
||||
replaceCallExpressionWithMemberExpression(j, j(path), property);
|
||||
};
|
||||
|
||||
// element.childAt(0) --> element.children[0]
|
||||
root.find(j.CallExpression, {
|
||||
callee: { property: { name: 'childAt' } },
|
||||
}).forEach(path => {
|
||||
// handle nested calls
|
||||
j(path)
|
||||
.find(j.CallExpression, {
|
||||
callee: { property: { name: 'childAt' } },
|
||||
})
|
||||
.forEach(replaceChildAtCall);
|
||||
|
||||
// handle top level call
|
||||
replaceChildAtCall(path);
|
||||
});
|
||||
|
||||
// Mock.ofType(MyType, MockBehavior.Strict) --> Mock.ofType(MyType)
|
||||
getCallExpressionByProperty(j, root, 'ofType').forEach(path => {
|
||||
const args = j(path).get().value.arguments;
|
||||
if (args.length !== 0) {
|
||||
path.replace(j.callExpression(j(path).get().value.callee, [args[0]]));
|
||||
const typeMoqImport = root.find(j.ImportDeclaration, {
|
||||
source: { value: 'typemoq' },
|
||||
});
|
||||
const mockBehaviors = root.find(j.Identifier, { name: 'MockBehavior' });
|
||||
if (mockBehaviors.length > 1) {
|
||||
removeImportSpecifier(j, root, typeMoqImport, 'MockBehavior');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// element.hasClass('className') --> element.classList.contains('className')
|
||||
getCallExpressionByProperty(j, root, 'hasClass').forEach(path => {
|
||||
replaceCallExpressionProperty(
|
||||
j,
|
||||
path,
|
||||
'classList.contains',
|
||||
j(path).get().value.arguments,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
// renderResult.simulate('click') --> await userEvent.click(renderResult.getByRole('button'));
|
||||
const replaceSimulateWithUserEvent = path => {
|
||||
getCallExpressionByProperty(j, j(path), 'simulate').forEach(p => {
|
||||
const args = j(p).get().value.arguments;
|
||||
const event = args[0].value;
|
||||
if (event === 'click') {
|
||||
addSourceImport(j, root, userEventSourceName, 'userEvent');
|
||||
const callObject = j(p).get().value.callee.object.name;
|
||||
const newCallObject = `${callObject}.getByRole`;
|
||||
const newCallExpression = j.callExpression(j.identifier(newCallObject), [
|
||||
j.literal('button'),
|
||||
]);
|
||||
const outsideCall = j.awaitExpression(
|
||||
j.callExpression(j.identifier('userEvent.click'), [newCallExpression]),
|
||||
);
|
||||
j(p).replaceWith(outsideCall);
|
||||
const testFunction = j(path).get().value.arguments[1];
|
||||
testFunction.async = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// it('should do something', () => { --> it('should do something', async () => {
|
||||
getCallExpressionByCalleeName(j, root, 'it').forEach(path => {
|
||||
replaceSimulateWithUserEvent(path);
|
||||
});
|
||||
|
||||
// test('should do something', () => { --> test('should do something', async () => {
|
||||
getCallExpressionByCalleeName(j, root, 'test').forEach(path => {
|
||||
replaceSimulateWithUserEvent(path);
|
||||
});
|
||||
|
||||
// reattach the comments to the first node
|
||||
const firstNode2 = getFirstNode();
|
||||
if (firstNode2 !== firstNode) {
|
||||
firstNode2.comments = comments;
|
||||
}
|
||||
|
||||
return root.toSource({ quote: 'single' });
|
||||
}
|
||||
|
||||
module.exports = transformer;
|
|
@ -205,6 +205,7 @@
|
|||
"src/tests/unit/common/visual-helper-toggle-config-builder.ts",
|
||||
"src/tests/unit/common/visualization-scan-result-store-data-builder.ts",
|
||||
"src/tests/unit/common/visualization-store-data-builder.ts",
|
||||
"src/tests/unit/mock-helpers/mock-module-helpers.tsx",
|
||||
"src/tests/unit/tests/DetailsView/assessment-report-builder-test-helper.ts",
|
||||
"src/tests/unit/tests/DetailsView/details-view-container-props-builder.ts",
|
||||
"src/tests/unit/tests/DetailsView/store-mocks.ts",
|
||||
|
|
864
yarn.lock
864
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче