Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@swimlane/cy-dom-diff

Package Overview
Dependencies
Maintainers
27
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@swimlane/cy-dom-diff - npm Package Compare versions

Comparing version 0.0.0 to 0.0.1

CHANGELOG.md

35

commands.js

@@ -1,21 +0,23 @@

import { clean, diff, disambiguateArgs, getDom } from './lib/util';
import { clean, disambiguateArgs, getDom } from './lib/util';
import { chaiDomMatch } from './lib/assertion';
chai.use(chaiDomMatch);
function logDiff(subject, re, options) {
if (!re.rec) {
function logDiff(name, state, $el, re, options) {
if (!re.pattern) {
throw new Error(`Cannot generate a diff against ${re}`);
}
const a = clean(getDom(subject), options);
const d = diff(a, re.rec(a));
const Actual = clean(getDom($el), options);
const Expected = re.replace(re.pattern);
const Difference = re.diff(Actual);
Cypress.log({
name: 'Dom Diff',
displayName: 'Dom Diff',
name,
message: 'DOM Difference',
$el,
// @ts-ignore
state: 'failed',
state,
consoleProps: () => {
return {
Pattern: re.pattern,
Regexp: re,
Actual: a,
Difference: d,
Subject: $el,
Expected,
Actual,
Difference,
};

@@ -25,3 +27,5 @@ },

}
Cypress.Commands.add('domDiff', { prevSubject: 'element' }, logDiff);
Cypress.Commands.add('domDiff', { prevSubject: 'element' }, ($el, re, options) => {
logDiff('domDiff', 'passed', $el, re, options);
});
Cypress.Commands.add('domMatch', { prevSubject: 'element' }, (subject, re, ...args) => {

@@ -34,5 +38,6 @@ const [message, options] = disambiguateArgs(args);

catch (e) {
// this is a hack to only show the log after all retries have failed
setTimeout(() => {
if (!e.onFail) {
logDiff(subject, re);
logDiff('domMatch', 'failed', subject, re, options);
}

@@ -42,4 +47,4 @@ });

}
logDiff(subject, re);
logDiff('domMatch', 'passed', subject, re, options);
});
});

@@ -15,4 +15,3 @@ import { getDom, clean, disambiguateArgs } from './util';

}
;
chai.Assertion.addMethod('domMatch', assertDomMatch);
};
import { clean } from './util';
import { createPatch } from 'diff';
function escape(source) {

@@ -7,21 +8,42 @@ if (source instanceof RegExp)

}
function formatPatch(text) {
return text
.replace(/^([^\n]+)\n([^\n]+)\n/m, '')
.replace(/--- \t\n/g, '') // headers
.replace(/\+\+\+ \t\n/g, '')
.split('\n')
.filter((x) => !x.includes('--- removed')) // Explanation
.filter((x) => !x.includes('+++ added'))
.filter((x) => !x.includes('@@ '))
.filter((x) => !x.includes('No newline at end of file'))
.join('\n')
.replace(/\n+$/, '\n')
.trim();
}
export class PatternRegExp extends RegExp {
constructor(source, pattern, template, matchers) {
constructor(source, pattern, matchers) {
super(source);
this.pattern = pattern;
this.template = template;
this.matchers = matchers;
this.rec = function (b) {
let pattern = this.pattern;
const m = b.match(this.template);
if (m) {
m.shift();
m.forEach((v, i) => {
const s = this.matchers[i];
const r = new RegExp(`^${s}$`);
pattern = pattern.replace(`__arg${i}__`, r.test(v) ? v : `\${/${s}/}`);
});
this.compare = function (left, right) {
if (right.includes('__arg')) {
const source = this.matchers.reduce((acc, m, i) => {
return acc.replace(`__arg${i}__`, escape(m));
}, escape(right));
return new RegExp(source).test(left);
}
return pattern;
return left === right;
};
this.replace = function (str) {
return this.matchers.reduce((acc, m, i) => {
return acc.replace(`__arg${i}__`, `\${${m}}`);
}, str);
};
this.diff = function (str) {
const options = {
comparator: (l, r) => this.compare(l, r),
};
const patch = createPatch('', this.pattern, str, '', '', options);
return formatPatch(this.replace(patch));
};
}

@@ -35,10 +57,6 @@ }

const pattern = clean(result.join(''));
let source = escape(pattern);
let s0 = source;
const matchers = args.map((arg, i) => {
source = source.replace(`__arg${i}__`, escape(arg));
s0 = s0.replace(`__arg${i}__`, '(.*)');
return arg.source || arg;
});
return new PatternRegExp(`^${source}$`, pattern, new RegExp(s0), matchers);
const source = args.reduce((acc, arg, i) => {
return acc.replace(`__arg${i}__`, escape(arg));
}, escape(pattern));
return new PatternRegExp(`^${source}$`, pattern, args);
}

@@ -1,3 +0,3 @@

import disparity from 'disparity';
import { getDiffableHTML } from '@open-wc/semantic-dom-diff/get-diffable-html';
import { getDiffableHTML, } from '@open-wc/semantic-dom-diff/get-diffable-html';
import unindent from 'strip-indent';
const { isJquery, isElement } = Cypress.dom;

@@ -11,29 +11,17 @@ export function getDom($el) {

}
return $el; // TODO: errror
return $el; // TODO: errror?
}
const domparser = new DOMParser();
export function clean(html, options) {
// const parser = new DOMParser();
// const doc = parser.parseFromString(html, 'text/html');
// return getDiffableHTML(doc.body).replace(/\s+/g, '\n').trim()
// return doc.body.innerHTML.replace(/\s+/g, '\n').trim();
return getDiffableHTML(html, options);
// Create a Node using DOMParser to avoid rendering
const doc = domparser.parseFromString(`<diff-container>${html}</diff-container>`, 'text/html');
return unindent(getDiffableHTML(doc.body.firstChild, options));
}
function removeExplanation(text) {
return text
.split('\n')
.filter((x) => !x.includes('--- removed'))
.filter((x) => !x.includes('+++ added'))
.filter((x) => !x.includes('@@ '))
.filter((x) => !x.includes('No newline at end of file'))
.join('\n')
.replace(/\n+$/, '\n');
}
export function diff(a, b) {
const textDiff = disparity.unifiedNoColor(a, b, {});
return removeExplanation(textDiff);
}
export function disambiguateArgs(args) {
// @ts-ignore
return args.length === 2 ? args :
(typeof args[0] === 'object' ? [undefined, args[0]] : [args[0], undefined]);
if (args.length === 2) {
return args;
}
return typeof args[0] === 'object'
? [undefined, args[0]]
: [args[0], undefined];
}
{
"name": "@swimlane/cy-dom-diff",
"version": "0.0.0",
"version": "0.0.1",
"description": "matching chunks of DOM against HTML; including dynamic content.",

@@ -26,3 +26,4 @@ "main": "index.js",

"np": "np && npm run clean",
"prepack": "npm run build"
"prepack": "npm run build",
"prepublishOnly": "npm run build"
},

@@ -33,7 +34,7 @@ "author": "J. Harshbarger",

"@open-wc/semantic-dom-diff": "^0.18.0",
"diff-dom": "^4.2.0",
"disparity": "^3.1.0",
"typescript": "^4.0.3"
"diff": "^4.0.2",
"strip-indent": "^3.0.0"
},
"devDependencies": {
"@types/diff": "^4.0.2",
"@types/faker": "^5.1.2",

@@ -48,3 +49,4 @@ "chg": "^0.4.0",

"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0"
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.0.3"
},

@@ -51,0 +53,0 @@ "peerDependencies": {

# cy-dom-diff
`cy-dom-diff` allows matching chunks of DOM against HTML; including dynamic content. `cy-dom-diff` consist of two parts. First is a Cypress command that matches a DOM element against a regular expression. When a DOM element is matched its HTML is normalized to produce consistent diffable output while maintaining sematic meaning. This includes:
`cy-dom-diff` allows matching chunks of DOM against HTML; including dynamic content.
## Introduction
`cy-dom-diff` consist of two parts. First is a Cypress command that matches a DOM element against a regular expression. When a DOM element is matched its HTML is normalized to produce consistent diffable output while maintaining sematic meaning. This includes:
* whitespace and newlines are normalized

@@ -43,2 +47,4 @@ * tags and attributes are printed on individual lines

![](diff.png)
### `cy.domDiff` Cypress command

@@ -45,0 +51,0 @@

import { DiffOptions } from '@open-wc/semantic-dom-diff/get-diffable-html';
import { PatternRegExp } from './lib/matchers';
import { clean, diff, disambiguateArgs, getDom } from './lib/util';
import { clean, disambiguateArgs, getDom } from './lib/util';
import { chaiDomMatch } from './lib/assertion';

@@ -9,23 +9,33 @@

type Options = Partial<Cypress.Loggable & Cypress.Timeoutable & DiffOptions> | undefined
type Options =
| Partial<Cypress.Loggable & Cypress.Timeoutable & DiffOptions>
| undefined;
function logDiff(subject: any, re: PatternRegExp, options?: Options) {
if (!re.rec) {
function logDiff(
name: string,
state: string,
$el: any,
re: PatternRegExp,
options?: Options
) {
if (!re.pattern) {
throw new Error(`Cannot generate a diff against ${re}`);
}
const a = clean(getDom(subject), options);
const d = diff(a, re.rec(a));
const Actual = clean(getDom($el), options);
const Expected = re.replace(re.pattern);
const Difference = re.diff(Actual);
Cypress.log({
name: 'Dom Diff',
displayName: 'Dom Diff',
name,
message: 'DOM Difference',
$el,
// @ts-ignore
state: 'failed',
state,
consoleProps: () => {
return {
Pattern: re.pattern,
Regexp: re,
Actual: a,
Difference: d,
Subject: $el,
Expected,
Actual,
Difference,
};

@@ -36,3 +46,9 @@ },

Cypress.Commands.add('domDiff', { prevSubject: 'element' }, logDiff);
Cypress.Commands.add(
'domDiff',
{ prevSubject: 'element' },
($el: any, re: PatternRegExp, options?: Options) => {
logDiff('domDiff', 'passed', $el, re, options);
}
);

@@ -43,3 +59,6 @@ Cypress.Commands.add(

(subject: any, re: PatternRegExp, ...args: [string | Options, Options]) => {
const [message, options] = disambiguateArgs(args as any) as [string | undefined, Options | undefined];
const [message, options] = disambiguateArgs(args as any) as [
string | undefined,
Options | undefined
];

@@ -50,5 +69,6 @@ cy.wrap(subject, options).should((el: any) => {

} catch (e) {
// this is a hack to only show the log after all retries have failed
setTimeout(() => {
if (!e.onFail) {
logDiff(subject, re);
logDiff('domMatch', 'failed', subject, re, options);
}

@@ -59,5 +79,5 @@ });

logDiff(subject, re);
logDiff('domMatch', 'passed', subject, re, options);
});
}
);

@@ -6,3 +6,7 @@ import { DiffOptions } from '@open-wc/semantic-dom-diff/get-diffable-html';

export const chaiDomMatch = (chai: Chai.ChaiStatic, utils: Chai.ChaiUtils) => {
function assertDomMatch(this: Chai.AssertionStatic, re: RegExp, ...args: [string | DiffOptions, DiffOptions]) {
function assertDomMatch(
this: Chai.AssertionStatic,
re: RegExp,
...args: [string | DiffOptions, DiffOptions]
) {
const [message, options] = disambiguateArgs(args) as [string, DiffOptions];

@@ -20,5 +24,5 @@

}
};
}
chai.Assertion.addMethod('domMatch', assertDomMatch);
};
import { clean } from './util';
import { createPatch } from 'diff';

@@ -8,2 +9,17 @@ function escape(source: RegExp | string) {

function formatPatch(text: string) {
return text
.replace(/^([^\n]+)\n([^\n]+)\n/m, '')
.replace(/--- \t\n/g, '') // headers
.replace(/\+\+\+ \t\n/g, '')
.split('\n')
.filter((x) => !x.includes('--- removed')) // Explanation
.filter((x) => !x.includes('+++ added'))
.filter((x) => !x.includes('@@ '))
.filter((x) => !x.includes('No newline at end of file'))
.join('\n')
.replace(/\n+$/, '\n')
.trim();
}
export class PatternRegExp extends RegExp {

@@ -13,3 +29,2 @@ constructor(

public readonly pattern: string,
public readonly template: RegExp,
public readonly matchers: any[]

@@ -20,18 +35,32 @@ ) {

rec = function (this: PatternRegExp, b: string) {
let pattern = this.pattern;
const m = b.match(this.template);
if (m) {
m.shift();
m.forEach((v, i) => {
const s = this.matchers[i];
const r = new RegExp(`^${s}$`);
pattern = pattern.replace(`__arg${i}__`, r.test(v) ? v : `\${/${s}/}`);
});
compare = function (this: PatternRegExp, left: string, right: string) {
if (right.includes('__arg')) {
const source = this.matchers.reduce((acc, m, i) => {
return acc.replace(`__arg${i}__`, escape(m));
}, escape(right));
return new RegExp(source).test(left);
}
return pattern;
return left === right;
};
replace = function (this: PatternRegExp, str: string) {
return this.matchers.reduce((acc, m, i) => {
return acc.replace(`__arg${i}__`, `\${${m}}`);
}, str);
};
diff = function (this: PatternRegExp, str: string) {
const options = {
comparator: (l: string, r: string) => this.compare(l, r),
};
const patch = createPatch('', this.pattern, str, '', '', options as any);
return formatPatch(this.replace(patch));
};
}
export function html(strings: TemplateStringsArray, ...args: any[]): PatternRegExp {
export function html(
strings: TemplateStringsArray,
...args: any[]
): PatternRegExp {
const result = [strings[0]];

@@ -42,10 +71,6 @@ args.forEach((arg, i) => {

const pattern = clean(result.join(''));
let source = escape(pattern);
let s0 = source;
const matchers = args.map((arg, i) => {
source = source.replace(`__arg${i}__`, escape(arg));
s0 = s0.replace(`__arg${i}__`, '(.*)');
return arg.source || arg;
});
return new PatternRegExp(`^${source}$`, pattern, new RegExp(s0), matchers);
const source = args.reduce((acc, arg, i) => {
return acc.replace(`__arg${i}__`, escape(arg));
}, escape(pattern));
return new PatternRegExp(`^${source}$`, pattern, args);
}

@@ -1,4 +0,8 @@

import disparity from 'disparity';
import { DiffOptions, getDiffableHTML } from '@open-wc/semantic-dom-diff/get-diffable-html';
import {
DiffOptions,
getDiffableHTML,
} from '@open-wc/semantic-dom-diff/get-diffable-html';
import unindent from 'strip-indent';
const { isJquery, isElement } = Cypress.dom;

@@ -13,33 +17,25 @@

}
return $el; // TODO: errror
return $el; // TODO: errror?
}
export function clean(html: string, options?: DiffOptions) {
// const parser = new DOMParser();
// const doc = parser.parseFromString(html, 'text/html');
// return getDiffableHTML(doc.body).replace(/\s+/g, '\n').trim()
// return doc.body.innerHTML.replace(/\s+/g, '\n').trim();
return getDiffableHTML(html, options);
}
const domparser = new DOMParser();
function removeExplanation(text: string) {
return text
.split('\n')
.filter((x) => !x.includes('--- removed'))
.filter((x) => !x.includes('+++ added'))
.filter((x) => !x.includes('@@ '))
.filter((x) => !x.includes('No newline at end of file'))
.join('\n')
.replace(/\n+$/, '\n');
export function clean(html: string, options?: DiffOptions): string {
// Create a Node using DOMParser to avoid rendering
const doc = domparser.parseFromString(
`<diff-container>${html}</diff-container>`,
'text/html'
);
return unindent(getDiffableHTML(doc.body.firstChild as Node, options));
}
export function diff(a: string, b: string) {
const textDiff = disparity.unifiedNoColor(a, b, {});
return removeExplanation(textDiff);
export function disambiguateArgs(
args: [string | object | undefined, object | undefined]
): [string | undefined, object | undefined] {
if (args.length === 2) {
return args as [string | undefined, object | undefined];
}
return typeof args[0] === 'object'
? [undefined, args[0]]
: [args[0], undefined];
}
export function disambiguateArgs(args: [string | object, object]): [string | undefined, object | undefined] {
// @ts-ignore
return args.length === 2 ? args :
(typeof args[0] === 'object' ? [undefined, args[0]] : [args[0], undefined]);
}
declare namespace Chai {
type DiffOptions = import('@open-wc/semantic-dom-diff/get-diffable-html').DiffOptions;
interface Assertion {

@@ -8,2 +8,2 @@ domMatch(re: RegExp, message?: string | DiffOptions): Assertion;

}
}
}

@@ -5,10 +5,12 @@ /// <reference types="cypress" />

type DiffOptions = import('@open-wc/semantic-dom-diff/get-diffable-html').DiffOptions;
type Options = Partial<Cypress.Loggable & Cypress.Timeoutable & DiffOptions> | undefined
type Options =
| Partial<Cypress.Loggable & Cypress.Timeoutable & DiffOptions>
| undefined;
interface Chainable<Subject> {
domDiff(re: RegExp, options: Options): Chainable<any>
domDiff(re: RegExp, options: Options): Chainable<any>;
domMatch(re: RegExp, message: string | Options): Chainable<any>
domMatch(re: RegExp, message?: string, options?: Options): Chainable<any>
domMatch(re: RegExp, message: string | Options): Chainable<any>;
domMatch(re: RegExp, message?: string, options?: Options): Chainable<any>;
}
}

@@ -5,2 +5,2 @@ /// <reference path="./chai-dom.d.ts" />

export * from '../lib/regexps';
export * from '../lib/matchers';
export * from '../lib/matchers';
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc