Fix wtr snapshots

This commit is contained in:
Alfonso Garcia-Caro
2021-09-27 15:49:33 +09:00
parent 3d1df74549
commit 2feaa9c664
7 changed files with 262 additions and 31 deletions

View File

@@ -2,6 +2,11 @@
export const snapshots = {};
snapshots["counter"] =
`%3Cdiv%3E%0A%20%20%20%20%3Cp%3EF%23%20counter%3C%2Fp%3E%0A%20%20%20%20%3Cp%3EValue%3A%205%3C%2Fp%3E%0A%20%20%20%20%3Cbutton%3EIncrement%3C%2Fbutton%3E%0A%20%20%20%20%3Cbutton%3EDecrement%3C%2Fbutton%3E%0A%20%20%20%20%3C%2Fdiv%3E`;
`<div>
<p>F# counter</p>
<p>Value: 5</p>
<button>Increment</button>
<button>Decrement</button>
</div>`;
/* end snapshot counter */

View File

@@ -2,6 +2,6 @@
export const snapshots = {};
snapshots["fable-element"] =
`%3Cp%3EElement%3C%2Fp%3E`;
`<p>Element</p>`;
/* end snapshot fable-element */

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<PackageId>Fable.Lit.Test</PackageId>
<Version>1.0.0</Version>
<PackageVersion>1.0.0-rc-005</PackageVersion>
<PackageVersion>1.0.0-rc-006</PackageVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

View File

@@ -1,3 +1,3 @@
### 1.0.0-rc-005
### 1.0.0-rc-006
* Release candidate

View File

@@ -58,7 +58,8 @@ module Mocha =
[<RequireQualifiedAccess>]
module Expect =
[<ImportAll("@web/test-runner-commands")>]
// [<ImportAll("@web/test-runner-commands")>]
[<ImportAll("./commands.js")>]
let private wtr: WebTestRunnerBindings = jsNative
let private cleanHtml (html: string) =
@@ -72,29 +73,23 @@ module Expect =
/// If the snapshot doesn't exist or tests are run with `--update-snapshots` option the snapshot will just be saved/updated.
let matchSnapshot (description: string) (name: string) (content: string) = promise {
let! config = wtr.getSnapshotConfig()
let! snapshot =
if config.updateSnapshots then Promise.lift null
else wtr.getSnapshot(name)
if isNull snapshot then
// Web test runner transforms the snapshot into a data URL to send it to the browser,
// and this can fail if we don't encode the content
return! wtr.saveSnapshot(name, JS.encodeURIComponent content)
if config.updateSnapshots then
return! wtr.saveSnapshot(name, content)
else
// Don't use wtr.compareSnapshot because that will update the snapshot
// without encoding the content even with a successful match
return
if not(snapshot = content) then
// Snapshots can be large, so use `brief` argument to hide them in the error message
// (Diffing should be displayed correctly)
Expect.AssertionError.Throw("match snapshot", description=description, actual=content, expected=snapshot, brief=true)
try
do! wtr.compareSnapshot(name, content)
with _ ->
let! snapshot = wtr.getSnapshot(name)
// Snapshots can be large, so use `brief` argument to hide them in the error message (diffing should be displayed correctly)
return Expect.AssertionError.Throw("match snapshot", description=description, actual=content, expected=snapshot, brief=true)
}
/// Compares `outerHML` of the element with the snapshot of the given name within the current file.
/// Compares `outerHML` (or `shadowRoot.innerHTML` for web components) of the element with the snapshot of the given name within the current file.
/// If the snapshot doesn't exist or tests are run with `--update-snapshots` option the snapshot will just be saved/updated.
let matchHtmlSnapshot (name: string) (el: HTMLElement) =
el.outerHTML |> cleanHtml |> matchSnapshot "outerHTML" name
let html, description =
match el.shadowRoot with
| null -> el.outerHTML, "outerHTML"
| shadowRoot -> shadowRoot.innerHTML, "shadowRoot.innerHTML"
/// Compares `shadowRoot.innerHTML` of the element with the snapshot of the given name within the current file.
/// If the snapshot doesn't exist or tests are run with `--update-snapshots` option the snapshot will just be saved/updated.
let matchShadowRootSnapshot (name: string) (el: Element) =
el.shadowRoot.innerHTML |> cleanHtml |> matchSnapshot "shadowRoor" name
html |> cleanHtml |> matchSnapshot description name

236
src/Lit.Test/commands.js Normal file
View File

@@ -0,0 +1,236 @@
// Adapted from: https://github.com/modernweb-dev/web/blob/master/packages/test-runner-commands/browser/commands.mjs
// Until PR is accepted: https://github.com/modernweb-dev/web/pull/1695
/*
MIT License
Copyright (c) 2020 modern-webdev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/* eslint-env browser, es2020 */
const PARAM_SESSION_ID = 'wtr-session-id';
const sessionId = new URL(window.location.href).searchParams.get(PARAM_SESSION_ID);
function isObject(payload) {
return payload != null && typeof payload === 'object';
}
export async function executeServerCommand(command, payload, pluginName) {
if (typeof sessionId !== 'string') {
throw new Error(
'Unable to execute server commands in a browser not controlled by the test runner. ' +
'Use the debug option from the watch menu to debug in a controlled browser.',
);
}
let sendMessageWaitForResponse;
try {
const webSocketModule = await import('/__web-dev-server__web-socket.js');
({ sendMessageWaitForResponse } = webSocketModule);
} catch (error) {
throw new Error(
'Could not setup web socket connection. Are you executing this test through Web Test Runner?',
);
}
try {
const response = await sendMessageWaitForResponse({
type: 'wtr-command',
sessionId,
command,
payload,
});
if (!response.executed) {
let msg;
if (pluginName) {
msg = `Unknown command ${command}. Add the ${pluginName} to your config.`;
} else {
msg = `Unknown command ${command}. Did you install a plugin to handle this command?`;
}
throw new Error(msg);
}
return response.result;
} catch (error) {
throw new Error(
`Error while executing command ${command}${
payload ? ` with payload ${JSON.stringify(payload)}` : ''
}: ${error.message}`,
);
}
}
export function setViewport(viewport) {
return executeServerCommand('set-viewport', viewport);
}
export function emulateMedia(media) {
return executeServerCommand('emulate-media', media);
}
export function setUserAgent(options) {
return executeServerCommand('set-user-agent', options);
}
export function sendKeys(options) {
return executeServerCommand('send-keys', options);
}
export function a11ySnapshot(options) {
return executeServerCommand('a11y-snapshot', options);
}
export function writeFile(options) {
return executeServerCommand('write-file', options, 'filePlugin from @web/test-runner-commands');
}
export function readFile(options) {
return executeServerCommand('read-file', options, 'filePlugin from @web/test-runner-commands');
}
export function removeFile(options) {
return executeServerCommand('remove-file', options, 'filePlugin from @web/test-runner-commands');
}
export function findAccessibilityNode(node, test) {
if (test(node)) return node;
for (const child of node.children || []) {
const foundNode = findAccessibilityNode(child, test);
if (foundNode) {
return foundNode;
}
}
return null;
}
let snapshotConfig;
let cachedSnapshots;
export async function getSnapshotConfig() {
if (!snapshotConfig) {
snapshotConfig = await executeServerCommand(
'get-snapshot-config',
undefined,
'snapshotPlugin from @web/test-runner-commands',
);
}
return snapshotConfig;
}
/**
* This regexp is used to capture the snapshots contents.
*
* snapshots\[[^\]]+] = (\n)? - snapshot definition. Sometimes the initial content backtick is placed in the next line
* (?<content>`[^`]+`) - capture the snapshot content, which is included between backticks "`"
* /gm - global and multiline
* @type {RegExp}
*/
const ESCAPE_REGEX = /snapshots\[[^\]]+] = (\n)?(?<content>`[^`]*`)/gm;
const escapeContent = content => {
[...content.matchAll(ESCAPE_REGEX)].forEach(({ groups: { content: itemContent } }) => {
content = content.replaceAll(itemContent, encodeURIComponent(itemContent));
});
return content;
};
export async function getSnapshots({ cache = true } = {}) {
if (cache && cachedSnapshots) {
return cachedSnapshots;
}
const result = await executeServerCommand(
'get-snapshots',
undefined,
'snapshotPlugin from @web/test-runner-commands',
);
if (typeof result?.content !== 'string') {
throw new Error('Expected a result as string');
}
const content = `${escapeContent(result.content)}/* ${Math.random()} */`;
const module = await import(`data:text/javascript;charset=utf-8,${content}`);
if (!module || !isObject(module.snapshots)) {
throw new Error('Expected snapshot result to be a module that exports an object.');
}
cachedSnapshots = module.snapshots;
return cachedSnapshots;
}
export async function getSnapshot(options) {
if (!isObject(options)) throw new Error('You must provide a payload object');
if (typeof options.name !== 'string') throw new Error('You must provide a snapshot name');
const snapshots = await getSnapshots(options);
return snapshots[options.name];
}
export async function saveSnapshot(options) {
if (!isObject(options)) throw new Error('You must provide a payload object');
if (typeof options.name !== 'string') throw new Error('You must provide a snapshot name');
if (options.content !== undefined && typeof options.content !== 'string')
throw new Error('You must provide a snapshot content');
// ensure snapshots for this file are loaded
const snapshots = await getSnapshots();
// store snapshot in-memory
snapshots[options.name] = options.content;
return executeServerCommand(
'save-snapshot',
options,
'snapshotPlugin from @web/test-runner-commands',
);
}
export function removeSnapshot(options) {
if (!isObject(options)) throw new Error('You must provide a payload object');
if (typeof options.name !== 'string') throw new Error('You must provide a snapshot name');
return saveSnapshot({ ...options, content: undefined });
}
export async function compareSnapshot({ name, content }) {
const currentSnapshot = await getSnapshot({ name });
if (currentSnapshot) {
const config = await getSnapshotConfig();
if (!config.updateSnapshots) {
if (currentSnapshot !== content) {
throw new Error(
`Snapshots for ${name} are not equal. \n\n` +
`Stored:\n${currentSnapshot}\n\n` +
`New:\n${content}`,
);
}
}
}
await saveSnapshot({ name, content });
}

View File

@@ -82,14 +82,9 @@ let DispatchCustomEvents () =
describe "LitElement" <| fun () ->
it "fable-element renders" <| fun () -> promise {
use! el = render_html $"<fable-element></fable-element>"
return! el.El |> Expect.matchShadowRootSnapshot "fable-element"
return! el.El |> Expect.matchHtmlSnapshot "fable-element"
}
// it "Can render LitElement as function" <| fun () -> promise {
// let! el = fixture_html $"{AttributeChanges()}"
// return! el |> Expect.matchShadowRootSnapshot "fel-attribute-changes"
// }
it "Reacts to attribute/property changes" <| fun () -> promise {
use! el = render_html $"<fel-attribute-changes></fel-attribute-changes>"
let el = el.El