diff --git a/js-api-spec/compile.test.ts b/js-api-spec/compile.test.ts new file mode 100644 index 0000000000..e65ba29bdc --- /dev/null +++ b/js-api-spec/compile.test.ts @@ -0,0 +1,416 @@ +// Copyright 2021 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import p from 'path'; +import {URL, pathToFileURL} from 'url'; + +import mock from 'mock-fs'; +import { + compile, + compileAsync, + compileString, + compileStringAsync, + OutputStyle, +} from 'sass'; + +import {skipForImpl} from './utils'; + +afterAll(mock.restore); + +skipForImpl('sass-embedded', () => { + describe('compileString', () => { + describe('success', () => { + describe('input', () => { + it('compiles SCSS by default', () => { + expect(compileString('$a: b; c {d: $a}').css).toBe('c {\n d: b;\n}'); + }); + + it('compiles SCSS with explicit syntax', () => { + expect(compileString('$a: b; c {d: $a}', {syntax: 'scss'}).css).toBe( + 'c {\n d: b;\n}' + ); + }); + + it('compiles SCSS with explicit syntax', () => { + expect(compileString('$a: b; c {d: $a}', {syntax: 'scss'}).css).toBe( + 'c {\n d: b;\n}' + ); + }); + + it('compiles indented syntax with explicit syntax', () => { + expect(compileString('a\n b: c', {syntax: 'indented'}).css).toBe( + 'a {\n b: c;\n}' + ); + }); + + it('compiles plain CSS with explicit syntax', () => { + expect(compileString('a {b: c}', {syntax: 'css'}).css).toBe( + 'a {\n b: c;\n}' + ); + }); + + it("doesn't take its syntax from the URL's extension", () => { + // Shouldn't parse the file as the indented syntax. + expect( + compileString('a {b: c}', {url: new URL('file:///foo.sass')}).css + ).toBe('a {\n b: c;\n}'); + }); + }); + + describe('loadedUrls', () => { + it('is empty with no URL', () => { + expect(compileString('a {b: c}').loadedUrls).toEqual([]); + }); + + it('contains the URL if one is passed', () => { + const url = new URL('file:///foo.scss'); + expect(compileString('a {b: c}', {url}).loadedUrls).toEqual([url]); + }); + + it('contains an immediate dependency', () => { + const url = pathToFileURL('input.scss'); + mock({'_other.scss': 'a {b: c}'}); + expect(compileString('@use "other"', {url}).loadedUrls).toEqual([ + url, + pathToFileURL('_other.scss'), + ]); + }); + + it('contains a transitive dependency', () => { + const url = pathToFileURL('input.scss'); + mock({ + '_midstream.scss': '@use "upstream"', + '_upstream.scss': 'a {b: c}', + }); + expect(compileString('@use "midstream"', {url}).loadedUrls).toEqual([ + url, + pathToFileURL('_midstream.scss'), + pathToFileURL('_upstream.scss'), + ]); + }); + + describe('contains a dependency only once', () => { + it('for @use', () => { + const url = pathToFileURL('input.scss'); + mock({ + '_left.scss': '@use "upstream"', + '_right.scss': '@use "upstream"', + '_upstream.scss': 'a {b: c}', + }); + expect( + compileString('@use "left"; @use "right"', {url}).loadedUrls + ).toEqual([ + url, + pathToFileURL('_left.scss'), + pathToFileURL('_upstream.scss'), + pathToFileURL('_right.scss'), + ]); + }); + + it('for @import', () => { + const url = pathToFileURL('input.scss'); + mock({ + '_left.scss': '@import "upstream"', + '_right.scss': '@import "upstream"', + '_upstream.scss': 'a {b: c}', + }); + expect( + compileString('@import "left"; @import "right"', {url}).loadedUrls + ).toEqual([ + url, + pathToFileURL('_left.scss'), + pathToFileURL('_upstream.scss'), + pathToFileURL('_right.scss'), + ]); + }); + }); + }); + + it('url is used to resolve relative loads', () => { + mock({'foo/bar/_other.scss': 'a {b: c}'}); + + expect( + compileString('@use "other";', { + url: pathToFileURL('foo/bar/style.scss'), + }).css + ).toBe('a {\n b: c;\n}'); + }); + + describe('loadPaths', () => { + it('is used to resolve loads', () => { + mock({'foo/bar/_other.scss': 'a {b: c}'}); + + expect( + compileString('@use "other";', { + loadPaths: ['foo/bar'], + }).css + ).toBe('a {\n b: c;\n}'); + }); + + it('resolves relative paths', () => { + mock({'foo/bar/_other.scss': 'a {b: c}'}); + + expect( + compileString('@use "bar/other";', { + loadPaths: ['foo'], + }).css + ).toBe('a {\n b: c;\n}'); + }); + + it("resolves loads using later paths if earlier ones don't match", () => { + mock({'baz/_other.scss': 'a {b: c}'}); + + expect( + compileString('@use "other";', { + loadPaths: ['foo', 'bar', 'baz'], + }).css + ).toBe('a {\n b: c;\n}'); + }); + + it("doesn't take precedence over loads relative to the url", () => { + mock({ + 'url/_other.scss': 'a {b: url}', + 'load-path/_other.scss': 'a {b: load path}', + }); + + expect( + compileString('@use "other";', { + loadPaths: ['load-path'], + url: pathToFileURL('url/input.scss'), + }).css + ).toBe('a {\n b: url;\n}'); + }); + + it('uses earlier paths in preference to later ones', () => { + mock({ + 'earlier/_other.scss': 'a {b: earlier}', + 'later/_other.scss': 'a {b: later}', + }); + + expect( + compileString('@use "other";', { + loadPaths: ['earlier', 'later'], + }).css + ).toBe('a {\n b: earlier;\n}'); + }); + }); + + it('recognizes the expanded output style', () => { + expect(compileString('a {b: c}', {style: 'expanded'}).css).toBe( + 'a {\n b: c;\n}' + ); + }); + }); + + describe('error', () => { + it('requires plain CSS with explicit syntax', () => { + expect(() => + compileString('$a: b; c {d: $a}', {syntax: 'css'}) + ).toThrowSassException({line: 0, noUrl: true}); + }); + + it('relative loads fail without a URL', () => { + mock({'other.scss': 'a {b: c}'}); + + expect(() => compileString('@use "other";')).toThrowSassException({ + line: 0, + noUrl: true, + }); + }); + + describe('includes source span information', () => { + it('in syntax errors', () => { + const url = pathToFileURL('foo.scss'); + expect(() => compileString('a {b:', {url})).toThrowSassException({ + line: 0, + url, + }); + }); + + it('in runtime errors', () => { + const url = pathToFileURL('foo.scss'); + expect(() => + compileString('@error "oh no"', {url}) + ).toThrowSassException({line: 0, url}); + }); + }); + + it('throws an error for an unrecognized style', () => { + expect(() => + compileString('a {b: c}', { + style: 'unrecognized style' as OutputStyle, + }) + ).toThrow(); + }); + + it("doesn't throw a Sass exception for an argument error", () => { + expect(() => + compileString('a {b: c}', { + style: 'unrecognized style' as OutputStyle, + }) + ).not.toThrowSassException(); + }); + + it('is an instance of Error', () => { + expect(() => compileString('a {b:')).toThrow(Error); + }); + }); + }); + + describe('compile', () => { + describe('success', () => { + it('compiles SCSS for a .scss file', () => { + mock({'input.scss': '$a: b; c {d: $a}'}); + expect(compile('input.scss').css).toBe('c {\n d: b;\n}'); + }); + + it('compiles SCSS for a file with an unknown extension', () => { + mock({'input.asdf': '$a: b; c {d: $a}'}); + expect(compile('input.asdf').css).toBe('c {\n d: b;\n}'); + }); + + it('compiles indented syntax for a .sass file', () => { + mock({'input.sass': 'a\n b: c'}); + expect(compile('input.sass').css).toBe('a {\n b: c;\n}'); + }); + + it('compiles plain CSS for a .css file', () => { + mock({'input.css': 'a {b: c}'}); + expect(compile('input.css').css).toBe('a {\n b: c;\n}'); + }); + + describe('loadedUrls', () => { + it("includes a relative path's URL", () => { + mock({'input.scss': 'a {b: c}'}); + expect(compile('input.scss').loadedUrls).toEqual([ + pathToFileURL('input.scss'), + ]); + }); + + it("includes an absolute path's URL", () => { + const path = p.resolve('input.scss'); + mock({[path]: 'a {b: c}'}); + expect(compile(path).loadedUrls).toEqual([pathToFileURL(path)]); + }); + + it('contains a dependency', () => { + mock({'input.scss': '@use "other"', '_other.scss': 'a {b: c}'}); + expect(compile('input.scss').loadedUrls).toEqual([ + pathToFileURL('input.scss'), + pathToFileURL('_other.scss'), + ]); + }); + }); + + it('the path is used to resolve relative loads', () => { + mock({ + 'foo/bar/input.scss': '@use "other"', + 'foo/bar/_other.scss': 'a {b: c}', + }); + + expect(compile('foo/bar/input.scss').css).toBe('a {\n b: c;\n}'); + }); + + describe('loadPaths', () => { + it('is used to resolve loads', () => { + mock({ + 'input.scss': '@use "other"', + 'foo/bar/_other.scss': 'a {b: c}', + }); + + expect(compile('input.scss', {loadPaths: ['foo/bar']}).css).toBe( + 'a {\n b: c;\n}' + ); + }); + + it("doesn't take precedence over loads relative to the entrypoint", () => { + mock({ + 'url/input.scss': '@use "other";', + 'url/_other.scss': 'a {b: url}', + 'load-path/_other.scss': 'a {b: load path}', + }); + + expect( + compile('url/input.scss', {loadPaths: ['load-path']}).css + ).toBe('a {\n b: url;\n}'); + }); + }); + }); + + describe('error', () => { + it('requires plain CSS for a .css file', () => { + mock({'input.css': '$a: b; c {d: $a}'}); + expect(() => compile('input.css')).toThrowSassException({ + line: 0, + url: pathToFileURL('input.css'), + }); + }); + + describe("includes the path's URL", () => { + it('in syntax errors', () => { + mock({'input.scss': 'a {b:'}); + expect(() => compile('input.scss')).toThrowSassException({ + line: 0, + url: pathToFileURL('input.scss'), + }); + }); + + it('in runtime errors', () => { + mock({'input.scss': '@error "oh no"'}); + expect(() => compile('input.scss')).toThrowSassException({ + line: 0, + url: pathToFileURL('input.scss'), + }); + }); + }); + }); + }); + + describe('compileStringAsync returns a promise that', () => { + it('succeeds when compilation succeeds', async () => { + await expect(compileStringAsync('a {b: c}')).resolves.toMatchObject({ + css: 'a {\n b: c;\n}', + }); + }); + + describe('fails when compilation fails', () => { + it('with a syntax error', async () => { + await expect(() => compileStringAsync('a {b:')).toThrowSassException({ + line: 0, + }); + }); + + it('with a runtime error', async () => { + await expect(() => + compileStringAsync('@error "oh no";') + ).toThrowSassException({ + line: 0, + }); + }); + }); + }); + + describe('compileAsync returns a promise that', () => { + it('succeeds when compilation succeeds', async () => { + mock({'input.scss': 'a {b: c}'}); + await expect(compileAsync('input.scss')).resolves.toMatchObject({css: 'a {\n b: c;\n}'}); + }); + + describe('fails when compilation fails', () => { + it('with a syntax error', async () => { + mock({'input.scss': 'a {b:'}); + await expect(() => compileAsync('input.scss')).toThrowSassException({ + line: 0, + }); + }); + + it('with a runtime error', async () => { + mock({'input.scss': '@error "oh no";'}); + await expect(() => compileAsync('input.scss')).toThrowSassException({ + line: 0, + }); + }); + }); + }); +}); diff --git a/js-api-spec/utils.ts b/js-api-spec/utils.ts new file mode 100644 index 0000000000..9403c3d9b6 --- /dev/null +++ b/js-api-spec/utils.ts @@ -0,0 +1,152 @@ +// Copyright 2021 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import {URL} from 'url'; + +import * as sass from 'sass'; + +/** The name of the implementation of Sass being tested. */ +export const sassImpl = sass.info.split('\t')[0]; + +declare global { + /* eslint-disable-next-line @typescript-eslint/no-namespace */ + namespace jest { + interface Matchers { + /** + * Matches a callback that throws a `sass.Exception`. + * + * If `line` is passed, asserts that the exception has a span that starts + * on that line. + * + * If `url` is passed, asserts that the exception has a span with the + * given URL. + */ + toThrowSassException(object?: {line?: number; url?: string | URL}): R; + + /** + * Matches a callback that throws a `sass.Exception` with a span that has + * no URL. + */ + toThrowSassException(object: {line?: number; noUrl: boolean}): R; + } + } +} + +interface ToThrowSassExceptionOptions { + line?: number; + url?: string | URL; + noUrl?: boolean; +} + +interface SyncExpectationResult { + pass: boolean; + message: () => string; +} + +expect.extend({ + toThrowSassException( + received: unknown, + options: ToThrowSassExceptionOptions = {} + ): SyncExpectationResult | Promise { + if (typeof received !== 'function') { + throw new Error('Received value must be a function'); + } + + try { + const result = received(); + if (result instanceof Promise) { + return result.then( + () => ({ + message: () => `expected ${received} to throw`, + pass: false, + }), + thrown => verifyThrown(thrown, options) + ); + } + } catch (thrown: unknown) { + return verifyThrown(thrown, options); + } + + return { + message: () => `expected ${received} to throw`, + pass: false, + }; + }, +}); + +/** + * Verifies that `thrown` matches the expectation of a `toThrowSassException` + * call. + */ +function verifyThrown( + thrown: unknown, + {line, url, noUrl}: ToThrowSassExceptionOptions +): SyncExpectationResult { + if (!(thrown instanceof sass.Exception)) { + return { + message: () => `expected ${thrown} to be a sass.Exception`, + pass: false, + }; + } else if ( + url && + thrown.span.url?.toString() !== url.toString() + ) { + return { + message: () => `expected ${url}, was ${thrown.span.url}:\n${thrown}`, + pass: false, + }; + } else if (noUrl && thrown.span.url) { + return { + message: () => `expected no URL:\n${thrown}`, + pass: false, + }; + } else if (line && thrown.span.start?.line !== line) { + return { + message: () => `expected to start on line ${line}, was ` + `${thrown.span.start?.line}:\n` + `${thrown}`, + pass: false, + }; + } else if (!thrown.sassMessage) { + return { + message: () => `expected a sassMessage field:\n${thrown}`, + pass: false, + }; + } else if (!thrown.sassStack) { + return { + message: () => `expected a sassStack field:\n${thrown}`, + pass: false, + }; + } else if (url) { + return { + message: () => `expected not ${url}:\n${thrown}`, + pass: true, + }; + } else if (noUrl) { + return { + message: () => `expected a URL:\n${thrown}`, + pass: true, + }; + } else if (line) { + return { + message: () => `expected not to start on line ${line}:\n${thrown}`, + pass: true, + }; + } else { + return { + message: () => `expected not to throw a sass.Exception:\n${thrown}`, + pass: true, + }; + } +} + +/** Skips the `block` of tests when running against the given `impl`. */ +export function skipForImpl( + impl: 'dart-sass' | 'sass-embedded', + block: () => void +): void { + if (sassImpl === impl) { + describe.skip(`[skipped for ${impl}]`, block); + } else { + block(); + } +} diff --git a/package-lock.json b/package-lock.json index bff80b30f9..4d88e3e0eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,16 @@ "@types/diff": "^4.0.2", "@types/js-yaml": "^3.12.5", "@types/lodash": "^4.14.168", + "@types/mock-fs": "^4.13.1", "@types/node": "^14.14.7", "@types/tmp": "^0.2.1", "@types/yargs": "^15.0.9", "del": "^6.0.0", "diff": "^5.0.0", + "expect": "^26.6.2", "js-yaml": "^3.14.0", "lodash": "^4.17.21", + "mock-fs": "^5.0.0", "node-hrx": "^0.1.0", "rxjs": "^6.5.5", "source-map": "^0.6.1", @@ -45,7 +48,6 @@ "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, "dependencies": { "@babel/highlight": "^7.10.4" } @@ -259,8 +261,7 @@ "node_modules/@babel/helper-validator-identifier": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" }, "node_modules/@babel/helper-validator-option": { "version": "7.12.17", @@ -283,7 +284,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.14.0", "chalk": "^2.0.0", @@ -294,7 +294,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -306,7 +305,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -320,7 +318,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -328,14 +325,12 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -344,7 +339,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, "engines": { "node": ">=4" } @@ -353,7 +347,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -1104,6 +1097,14 @@ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, + "node_modules/@types/mock-fs": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz", + "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "14.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.1.tgz", @@ -1124,8 +1125,7 @@ "node_modules/@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" }, "node_modules/@types/tmp": { "version": "0.2.1", @@ -2522,7 +2522,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, "engines": { "node": ">= 10.14.2" } @@ -3285,7 +3284,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-styles": "^4.0.0", @@ -4667,7 +4665,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", @@ -4745,7 +4742,6 @@ "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, "engines": { "node": ">= 10.14.2" } @@ -4823,7 +4819,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^26.6.2", @@ -4838,7 +4833,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", @@ -4888,7 +4882,6 @@ "version": "26.0.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true, "engines": { "node": ">= 10.14.2" } @@ -5185,8 +5178,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -5672,6 +5664,11 @@ "node": ">=10" } }, + "node_modules/mock-fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.0.0.tgz", + "integrity": "sha512-A5mm/SpSDwwc/klSaEvvKMGQQtiGiQy8UcDAd/vpVO1fV+4zaHjt39yKgCSErFzv2zYxZIUx9Ud/7ybeHBf8Fg==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6255,7 +6252,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -6386,8 +6382,7 @@ "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/read-pkg": { "version": "5.2.0", @@ -7486,7 +7481,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -7498,7 +7492,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, "engines": { "node": ">=8" } @@ -8508,7 +8501,6 @@ "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, "requires": { "@babel/highlight": "^7.10.4" } @@ -8706,8 +8698,7 @@ "@babel/helper-validator-identifier": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" }, "@babel/helper-validator-option": { "version": "7.12.17", @@ -8730,7 +8721,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.0", "chalk": "^2.0.0", @@ -8741,7 +8731,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -8750,7 +8739,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -8761,7 +8749,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -8769,26 +8756,22 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -9420,6 +9403,14 @@ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, + "@types/mock-fs": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz", + "integrity": "sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "14.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.1.tgz", @@ -9440,8 +9431,7 @@ "@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==" }, "@types/tmp": { "version": "0.2.1", @@ -10478,8 +10468,7 @@ "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" }, "dir-glob": { "version": "3.0.1", @@ -11044,7 +11033,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, "requires": { "@jest/types": "^26.6.2", "ansi-styles": "^4.0.0", @@ -12096,7 +12084,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", @@ -12158,8 +12145,7 @@ "jest-get-type": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" }, "jest-haste-map": { "version": "26.6.2", @@ -12223,7 +12209,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, "requires": { "chalk": "^4.0.0", "jest-diff": "^26.6.2", @@ -12235,7 +12220,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", @@ -12268,8 +12252,7 @@ "jest-regex-util": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==" }, "jest-resolve": { "version": "26.6.2", @@ -12519,8 +12502,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.1", @@ -12887,6 +12869,11 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mock-fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.0.0.tgz", + "integrity": "sha512-A5mm/SpSDwwc/klSaEvvKMGQQtiGiQy8UcDAd/vpVO1fV+4zaHjt39yKgCSErFzv2zYxZIUx9Ud/7ybeHBf8Fg==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -13326,7 +13313,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, "requires": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -13421,8 +13407,7 @@ "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "read-pkg": { "version": "5.2.0", @@ -14293,7 +14278,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, "requires": { "escape-string-regexp": "^2.0.0" }, @@ -14301,8 +14285,7 @@ "escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" } } }, diff --git a/package.json b/package.json index 417e9d5e9e..34773865e0 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,16 @@ "@types/diff": "^4.0.2", "@types/js-yaml": "^3.12.5", "@types/lodash": "^4.14.168", + "@types/mock-fs": "^4.13.1", "@types/node": "^14.14.7", "@types/tmp": "^0.2.1", "@types/yargs": "^15.0.9", "del": "^6.0.0", "diff": "^5.0.0", + "expect": "^26.6.2", "js-yaml": "^3.14.0", "lodash": "^4.17.21", + "mock-fs": "^5.0.0", "node-hrx": "^0.1.0", "rxjs": "^6.5.5", "source-map": "^0.6.1",