From cf531a08b1a73ea89403ee762e4315b7bb5b3eeb Mon Sep 17 00:00:00 2001
From: "Yuan (Bob) Gong"
Date: Tue, 25 Feb 2020 14:38:48 +0800
Subject: [PATCH] [Frontend] snapshot diff setup (#3166)
* [Frontend] snapshot diff setup
* Rename
* Fix test
* Update README about recommended setup
---
frontend/README.md | 9 +-
frontend/package.json | 1 +
frontend/src/TestUtils.tsx | 42 ++++++++
frontend/src/components/SideNav.test.tsx | 33 +++++-
.../__snapshots__/SideNav.test.tsx.snap | 28 -----
.../components/viewers/Tensorboard.test.tsx | 63 +++++++++--
.../__snapshots__/Tensorboard.test.tsx.snap | 54 ----------
frontend/src/pages/GettingStarted.test.tsx | 100 ++++++++++++++++--
.../GettingStarted.test.tsx.snap | 89 ----------------
9 files changed, 223 insertions(+), 196 deletions(-)
diff --git a/frontend/README.md b/frontend/README.md
index 95166bd933c7..f0ff21c9dd9d 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -100,13 +100,16 @@ To understand more what prettier is: [What is Prettier](https://prettier.io/docs
this project's config automatically.
Recommend setting the following in [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations) for vscode to autoformat on save.
```
- "[typescript]": {
- "editor.formatOnSave": true,
+ "[typescript]": {
+ "editor.formatOnSave": true,
+ "files.trimTrailingWhitespace": false,
},
"[typescriptreact]": {
- "editor.formatOnSave": true,
+ "editor.formatOnSave": true,
+ "files.trimTrailingWhitespace": false,
},
```
+ Also, vscode builtin trailing whitespace [conflicts with jest inline snapshot](https://github.com/Microsoft/vscode/issues/52711), so recommend disabling it.
- For others, refer to https://prettier.io/docs/en/editors.html
### Format Code Manually
diff --git a/frontend/package.json b/frontend/package.json
index 0ad9c78bfd2c..892bac924473 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -110,6 +110,7 @@
],
"snapshotSerializers": [
"./src/__serializers__/mock-function",
+ "snapshot-diff/serializer.js",
"enzyme-to-json/serializer"
]
},
diff --git a/frontend/src/TestUtils.tsx b/frontend/src/TestUtils.tsx
index 3f1646d40ee1..f82e2219bf7b 100644
--- a/frontend/src/TestUtils.tsx
+++ b/frontend/src/TestUtils.tsx
@@ -26,6 +26,7 @@ import { match } from 'react-router';
import { mount, ReactWrapper } from 'enzyme';
import { object } from 'prop-types';
import { format } from 'prettier';
+import snapshotDiff from 'snapshot-diff';
export default class TestUtils {
/**
@@ -110,3 +111,44 @@ export default class TestUtils {
export function formatHTML(html: string): string {
return format(html, { parser: 'html' });
}
+
+/**
+ * Generate diff text for two HTML strings.
+ * Recommend providing base and update annotations to clarify context in the diff directly.
+ */
+export function diffHTML({
+ base,
+ update,
+ baseAnnotation,
+ updateAnnotation,
+}: {
+ base: string;
+ baseAnnotation?: string;
+ update: string;
+ updateAnnotation?: string;
+}) {
+ return diff({
+ base: formatHTML(base),
+ update: formatHTML(update),
+ baseAnnotation,
+ updateAnnotation,
+ });
+}
+
+export function diff({
+ base,
+ update,
+ baseAnnotation,
+ updateAnnotation,
+}: {
+ base: string;
+ baseAnnotation?: string;
+ update: string;
+ updateAnnotation?: string;
+}) {
+ return snapshotDiff(base, update, {
+ stablePatchmarks: true, // Avoid line numbers in diff, so that diffs are stable against irrelevant changes
+ aAnnotation: baseAnnotation,
+ bAnnotation: updateAnnotation,
+ });
+}
diff --git a/frontend/src/components/SideNav.test.tsx b/frontend/src/components/SideNav.test.tsx
index ca442a31fb5f..b04ddc210e27 100644
--- a/frontend/src/components/SideNav.test.tsx
+++ b/frontend/src/components/SideNav.test.tsx
@@ -17,13 +17,12 @@
import * as React from 'react';
import SideNav, { css } from './SideNav';
-import TestUtils from '../TestUtils';
+import TestUtils, { diff } from '../TestUtils';
import { Apis } from '../lib/Apis';
import { LocalStorage } from '../lib/LocalStorage';
import { ReactWrapper, ShallowWrapper, shallow } from 'enzyme';
import { RoutePage } from './Router';
import { RouterProps } from 'react-router';
-import snapshotDiff from 'snapshot-diff';
const wideWidth = 1000;
const narrowWidth = 200;
@@ -259,10 +258,36 @@ describe('SideNav', () => {
buildInfoSpy.mockImplementationOnce(() => Promise.reject('Error when fetching build info'));
tree = shallow();
- const baseTree = tree.debug();
+ const base = tree.debug();
await TestUtils.flushPromises();
tree.update();
- expect(snapshotDiff(baseTree, tree.debug())).toMatchSnapshot();
+ expect(diff({ base, update: tree.debug() })).toMatchInlineSnapshot(`
+ Snapshot Diff:
+ - Expected
+ + Received
+
+ @@ --- --- @@
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+ Report an Issue
+
+ `);
});
it('displays the frontend commit hash if the api server hash is not returned', async () => {
diff --git a/frontend/src/components/__snapshots__/SideNav.test.tsx.snap b/frontend/src/components/__snapshots__/SideNav.test.tsx.snap
index cd6f1565020b..7a073125cc14 100644
--- a/frontend/src/components/__snapshots__/SideNav.test.tsx.snap
+++ b/frontend/src/components/__snapshots__/SideNav.test.tsx.snap
@@ -1,33 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`SideNav populates the cluster information using the response from the system endpoints 1`] = `
-"Snapshot Diff:
-- First value
-+ Second value
-
-@@ -64,10 +64,20 @@
-
-
-
-
-
-+
-+
-+
-
-
-
- Report an Issue
- "
-`;
-
exports[`SideNav populates the display build information using the response from the healthz endpoint 1`] = `
{
let tree: ReactWrapper | ShallowWrapper;
@@ -50,10 +49,26 @@ describe('Tensorboard', () => {
const getAppMock = () => Promise.resolve({ podAddress: '', tfVersion: '' });
jest.spyOn(Apis, 'getTensorboardApp').mockImplementation(getAppMock);
tree = shallow();
- const baseTree = tree.debug();
+ const base = tree.debug();
await TestUtils.flushPromises();
- expect(snapshotDiff(tree.debug(), baseTree)).toMatchSnapshot();
+ expect(diff({ base, update: tree.debug() })).toMatchInlineSnapshot(`
+ Snapshot Diff:
+ - Expected
+ + Received
+
+ @@ --- --- @@
+
+
+
+
+
+ -
+ +
+
+
+
+ `);
});
it('does not break on empty data', async () => {
@@ -61,10 +76,26 @@ describe('Tensorboard', () => {
jest.spyOn(Apis, 'getTensorboardApp').mockImplementation(getAppMock);
const config = { type: PlotType.TENSORBOARD, url: '' };
tree = shallow();
- const baseTree = tree.debug();
+ const base = tree.debug();
await TestUtils.flushPromises();
- expect(snapshotDiff(tree.debug(), baseTree)).toMatchSnapshot();
+ expect(diff({ base, update: tree.debug() })).toMatchInlineSnapshot(`
+ Snapshot Diff:
+ - Expected
+ + Received
+
+ @@ --- --- @@
+
+
+
+
+
+ -
+ +
+
+
+
+ `);
});
it('shows a link to the tensorboard instance if exists', async () => {
@@ -82,10 +113,26 @@ describe('Tensorboard', () => {
const getAppMock = () => Promise.resolve({ podAddress: '', tfVersion: '' });
const spy = jest.spyOn(Apis, 'getTensorboardApp').mockImplementation(getAppMock);
tree = shallow();
- const baseTree = tree.debug();
+ const base = tree.debug();
await TestUtils.flushPromises();
- expect(snapshotDiff(tree.debug(), baseTree)).toMatchSnapshot();
+ expect(diff({ base, update: tree.debug() })).toMatchInlineSnapshot(`
+ Snapshot Diff:
+ - Expected
+ + Received
+
+ @@ --- --- @@
+
+
+
+
+
+ -
+ +
+
+
+
+ `);
expect(spy).toHaveBeenCalledWith(config.url);
});
diff --git a/frontend/src/components/viewers/__snapshots__/Tensorboard.test.tsx.snap b/frontend/src/components/viewers/__snapshots__/Tensorboard.test.tsx.snap
index ce2c0b3ce0ad..ff0e334cc18d 100644
--- a/frontend/src/components/viewers/__snapshots__/Tensorboard.test.tsx.snap
+++ b/frontend/src/components/viewers/__snapshots__/Tensorboard.test.tsx.snap
@@ -111,42 +111,6 @@ exports[`Tensorboard base component snapshot 1`] = `
`;
-exports[`Tensorboard does not break on empty data 1`] = `
-"Snapshot Diff:
-- First value
-+ Second value
-
-@@ -53,9 +53,9 @@
-
-
-
-
-
--
-+
-
-
- "
-`;
-
-exports[`Tensorboard does not break on no config 1`] = `
-"Snapshot Diff:
-- First value
-+ Second value
-
-@@ -53,9 +53,9 @@
-
-
-
-
-
--
-+
-
-
- "
-`;
-
exports[`Tensorboard shows a link to the tensorboard instance if exists 1`] = `
@@ -218,21 +182,3 @@ exports[`Tensorboard shows a link to the tensorboard instance if exists 1`] = `
`;
-
-exports[`Tensorboard shows start button if no instance exists 1`] = `
-"Snapshot Diff:
-- First value
-+ Second value
-
-@@ -53,9 +53,9 @@
-
-
-
-
-
--
-+
-
-
- "
-`;
diff --git a/frontend/src/pages/GettingStarted.test.tsx b/frontend/src/pages/GettingStarted.test.tsx
index 5d8c624519ae..c1820be517ad 100644
--- a/frontend/src/pages/GettingStarted.test.tsx
+++ b/frontend/src/pages/GettingStarted.test.tsx
@@ -1,11 +1,10 @@
import React from 'react';
import { GettingStarted } from './GettingStarted';
-import TestUtils, { formatHTML } from '../TestUtils';
+import TestUtils, { diffHTML } from '../TestUtils';
import { render } from '@testing-library/react';
import { PageProps } from './Page';
import { Apis } from '../lib/Apis';
import { ApiListPipelinesResponse } from '../apis/pipeline/api';
-import snapshotDiff from 'snapshot-diff';
const PATH_BACKEND_CONFIG = '../../../backend/src/apiserver/config/sample_config.json';
const PATH_FRONTEND_CONFIG = '../config/sample_config_from_backend.json';
@@ -60,12 +59,75 @@ describe('GettingStarted page', () => {
return Promise.resolve(response);
});
const { container } = render();
- const initialHtml = container.innerHTML;
+ const base = container.innerHTML;
await TestUtils.flushPromises();
expect(pipelineListSpy.mock.calls).toMatchSnapshot();
- expect(
- snapshotDiff(formatHTML(initialHtml), formatHTML(container.innerHTML)),
- ).toMatchSnapshot();
+ expect(diffHTML({ base, update: container.innerHTML })).toMatchInlineSnapshot(`
+ Snapshot Diff:
+ - Expected
+ + Received
+
+ @@ --- --- @@
+ Demonstrations and Tutorials
+ This section contains demo and tutorial pipelines.
+ Demos - Try an end-to-end demonstration pipeline.
+
+