@genesislcap/foundation-testing 14.137.1-alpha-ee4af66.0 → 14.137.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/component/fixture.js +90 -0
- package/dist/cjs/component/index.js +4 -0
- package/dist/cjs/index.federated.js +24 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/jsdom/setup.js +139 -0
- package/dist/cjs/playwright/config.js +43 -0
- package/dist/cjs/playwright/index.js +8 -0
- package/dist/cjs/playwright/setup.js +33 -0
- package/dist/cjs/playwright/test.js +61 -0
- package/dist/cjs/suite/index.js +4 -0
- package/dist/cjs/suite/suite.js +181 -0
- package/dist/cjs/tsm/index.js +27 -0
- package/dist/cjs/utils/harness.js +71 -0
- package/dist/cjs/utils/index.js +7 -0
- package/dist/cjs/utils/logger.js +11 -0
- package/dist/cjs/utils/promise.js +25 -0
- package/dist/cjs/utils/types.js +3 -0
- package/dist/cjs/uvu/index.js +4 -0
- package/dist/cjs/uvu/uvu.js +53 -0
- package/package.json +7 -4
- package/tsconfig.cjs.json +10 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fixture = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fast_element_1 = require("@microsoft/fast-element");
|
|
6
|
+
const fast_foundation_1 = require("@microsoft/fast-foundation");
|
|
7
|
+
function findElement(view) {
|
|
8
|
+
let current = view.firstChild;
|
|
9
|
+
while (current !== null && current.nodeType !== 1) {
|
|
10
|
+
current = current.nextSibling;
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
}
|
|
14
|
+
function isElementRegistry(obj) {
|
|
15
|
+
return typeof obj.register === 'function';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a test fixture suitable for testing custom elements, templates, and bindings.
|
|
19
|
+
* @param templateNameOrRegistry - An HTML template or single element name to create the fixture for.
|
|
20
|
+
* @param options - Enables customizing fixture creation behavior.
|
|
21
|
+
* @returns A promise of the test fixture {@link Fixture}.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
* @remarks
|
|
25
|
+
* Yields control to the caller one Microtask later, in order to
|
|
26
|
+
* ensure that the DOM has settled. This has changed in the latest version of FAST!
|
|
27
|
+
*/
|
|
28
|
+
function fixture(templateNameOrRegistry, options = {}) {
|
|
29
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
const document = options.document || globalThis.document;
|
|
31
|
+
const parent = options.parent || document.createElement('div');
|
|
32
|
+
const source = options.source || {};
|
|
33
|
+
const context = options.context || fast_element_1.defaultExecutionContext;
|
|
34
|
+
if (typeof templateNameOrRegistry === 'string') {
|
|
35
|
+
const html = `<${templateNameOrRegistry}></${templateNameOrRegistry}>`;
|
|
36
|
+
templateNameOrRegistry = new fast_element_1.ViewTemplate(html, []);
|
|
37
|
+
}
|
|
38
|
+
else if (isElementRegistry(templateNameOrRegistry)) {
|
|
39
|
+
templateNameOrRegistry = [templateNameOrRegistry];
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(templateNameOrRegistry)) {
|
|
42
|
+
const first = templateNameOrRegistry[0];
|
|
43
|
+
const ds = options.designSystem || fast_foundation_1.DesignSystem.getOrCreate(parent);
|
|
44
|
+
let prefix = '';
|
|
45
|
+
ds.register(templateNameOrRegistry, {
|
|
46
|
+
register(container, dsContext) {
|
|
47
|
+
prefix = dsContext.elementPrefix;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const elementName = `${prefix}-${first.definition.baseName}`;
|
|
51
|
+
const html = `<${elementName}></${elementName}>`;
|
|
52
|
+
templateNameOrRegistry = new fast_element_1.ViewTemplate(html, []);
|
|
53
|
+
}
|
|
54
|
+
const view = templateNameOrRegistry.create();
|
|
55
|
+
const element = findElement(view);
|
|
56
|
+
let isConnected = false;
|
|
57
|
+
view.bind(source, context);
|
|
58
|
+
view.appendTo(parent);
|
|
59
|
+
customElements.upgrade(parent);
|
|
60
|
+
// Hook into the Microtask Queue to ensure the DOM is settled
|
|
61
|
+
// before yielding control to the caller.
|
|
62
|
+
yield Promise.resolve();
|
|
63
|
+
const connect = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
if (isConnected) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
isConnected = true;
|
|
68
|
+
document.body.appendChild(parent);
|
|
69
|
+
yield Promise.resolve();
|
|
70
|
+
});
|
|
71
|
+
const disconnect = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
if (!isConnected) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
isConnected = false;
|
|
76
|
+
document.body.removeChild(parent);
|
|
77
|
+
yield Promise.resolve();
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
document,
|
|
81
|
+
template: templateNameOrRegistry,
|
|
82
|
+
view,
|
|
83
|
+
parent,
|
|
84
|
+
element,
|
|
85
|
+
connect,
|
|
86
|
+
disconnect,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
exports.fixture = fixture;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
2
|
+
if (k2 === undefined) k2 = k;
|
|
3
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
4
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
5
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
6
|
+
}
|
|
7
|
+
Object.defineProperty(o, k2, desc);
|
|
8
|
+
}) : (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
o[k2] = m[k];
|
|
11
|
+
}));
|
|
12
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
13
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
14
|
+
}) : function(o, v) {
|
|
15
|
+
o["default"] = v;
|
|
16
|
+
});
|
|
17
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
18
|
+
if (mod && mod.__esModule) return mod;
|
|
19
|
+
var result = {};
|
|
20
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
21
|
+
__setModuleDefault(result, mod);
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
Promise.resolve().then(() => __importStar(require('./index')));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Registration = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* Implemented by objects that wish to register dependencies in the container
|
|
7
|
+
* by creating resolvers.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
var fast_foundation_1 = require("@microsoft/fast-foundation");
|
|
11
|
+
Object.defineProperty(exports, "Registration", { enumerable: true, get: function () { return fast_foundation_1.Registration; } });
|
|
12
|
+
tslib_1.__exportStar(require("./component"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./suite"), exports);
|
|
14
|
+
tslib_1.__exportStar(require("./utils"), exports);
|
|
15
|
+
tslib_1.__exportStar(require("./uvu"), exports);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.teardown = exports.setup = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const jsdom_1 = require("jsdom");
|
|
6
|
+
/**
|
|
7
|
+
* Jsdom setup.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
const setup = () => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
11
|
+
const { window } = new jsdom_1.JSDOM('<!DOCTYPE html><html lang="en"><head></head><body></body></html>', {
|
|
12
|
+
pretendToBeVisual: true,
|
|
13
|
+
url: 'https://localhost',
|
|
14
|
+
});
|
|
15
|
+
window.matchMedia = () => ({
|
|
16
|
+
matches: false,
|
|
17
|
+
addListener: () => { },
|
|
18
|
+
removeListener: () => { },
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* There may have been a change to how the `CSSOM.parse(rule)` works, and how invalid rules are treated. Here we'll be
|
|
22
|
+
* more torrential of such issues given the limitations of JSDOM.
|
|
23
|
+
*/
|
|
24
|
+
const cssInsertRule = window.CSSStyleSheet.prototype.insertRule;
|
|
25
|
+
window.CSSStyleSheet.prototype.insertRule = function (rule, index) {
|
|
26
|
+
try {
|
|
27
|
+
return cssInsertRule.call(this, rule, index);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
if (rule.includes('@import')) {
|
|
31
|
+
/**
|
|
32
|
+
* Longstanding issue it seems, https://github.com/jsdom/jsdom/issues/2124
|
|
33
|
+
*/
|
|
34
|
+
console.log('Please note JSDOM does not support @import, skipping insertion.');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
/**
|
|
38
|
+
* Unknown malformation.
|
|
39
|
+
*/
|
|
40
|
+
console.log('The CSS rule you are inserting is likely malformed', rule);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Return index as if were successful.
|
|
44
|
+
*/
|
|
45
|
+
return index;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
window.ResizeObserver = class ResizeObserver {
|
|
49
|
+
constructor(callback) {
|
|
50
|
+
this.callback = callback;
|
|
51
|
+
}
|
|
52
|
+
observe() { }
|
|
53
|
+
disconnect() { }
|
|
54
|
+
triggerResize(width, height) {
|
|
55
|
+
this.callback([{ contentRect: { width, height } }]);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
globalThis.window = window;
|
|
59
|
+
globalThis.customElements = window.customElements;
|
|
60
|
+
globalThis.CSSImportRule = window.CSSImportRule;
|
|
61
|
+
globalThis.CSSMediaRule = window.CSSMediaRule;
|
|
62
|
+
globalThis.CSSRule = window.CSSRule;
|
|
63
|
+
globalThis.CSSStyleDeclaration = window.CSSStyleDeclaration;
|
|
64
|
+
globalThis.CSSStyleRule = window.CSSStyleRule;
|
|
65
|
+
globalThis.CSSStyleSheet = window.CSSStyleSheet;
|
|
66
|
+
globalThis.CustomEvent = window.CustomEvent;
|
|
67
|
+
globalThis.document = window.document;
|
|
68
|
+
globalThis.Document = window.Document;
|
|
69
|
+
globalThis.DocumentFragment = window.DocumentFragment;
|
|
70
|
+
globalThis.HTMLDialogElement = window.HTMLDialogElement;
|
|
71
|
+
globalThis.HTMLElement = window.HTMLElement;
|
|
72
|
+
globalThis.HTMLInputElement = window.HTMLInputElement;
|
|
73
|
+
globalThis.HTMLOptionElement = window.HTMLOptionElement;
|
|
74
|
+
globalThis.HTMLSelectElement = window.HTMLSelectElement;
|
|
75
|
+
globalThis.HTMLStyleElement = window.HTMLStyleElement;
|
|
76
|
+
globalThis.KeyboardEvent = window.KeyboardEvent;
|
|
77
|
+
globalThis.localStorage = window.localStorage;
|
|
78
|
+
globalThis.sessionStorage = window.sessionStorage;
|
|
79
|
+
globalThis.MutationObserver = window.MutationObserver;
|
|
80
|
+
globalThis.navigator = window.navigator;
|
|
81
|
+
globalThis.location = window.location;
|
|
82
|
+
globalThis.Node = window.Node;
|
|
83
|
+
globalThis.Option = window.Option;
|
|
84
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
85
|
+
globalThis.ShadowRoot = window.ShadowRoot;
|
|
86
|
+
// Env Variables
|
|
87
|
+
globalThis.FORCE_HTTP = false;
|
|
88
|
+
globalThis.HTTP_CONFIG = '';
|
|
89
|
+
});
|
|
90
|
+
exports.setup = setup;
|
|
91
|
+
/**
|
|
92
|
+
* Jsdom teardown / cleanup.
|
|
93
|
+
* @public
|
|
94
|
+
* @remarks
|
|
95
|
+
* This strictly is not required, but may be useful for certain test runs. If used, be mindful that you will need to
|
|
96
|
+
* call setup again before the next test run.
|
|
97
|
+
*/
|
|
98
|
+
const teardown = () => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
99
|
+
delete window.matchMedia;
|
|
100
|
+
delete globalThis.window;
|
|
101
|
+
delete globalThis.customElements;
|
|
102
|
+
delete globalThis.CSSImportRule;
|
|
103
|
+
delete globalThis.CSSMediaRule;
|
|
104
|
+
delete globalThis.CSSRule;
|
|
105
|
+
delete globalThis.CSSStyleDeclaration;
|
|
106
|
+
delete globalThis.CSSStyleRule;
|
|
107
|
+
delete globalThis.CSSStyleSheet;
|
|
108
|
+
delete globalThis.CustomEvent;
|
|
109
|
+
delete globalThis.document;
|
|
110
|
+
delete globalThis.Document;
|
|
111
|
+
delete globalThis.DocumentFragment;
|
|
112
|
+
delete globalThis.HTMLDialogElement;
|
|
113
|
+
delete globalThis.HTMLElement;
|
|
114
|
+
delete globalThis.HTMLInputElement;
|
|
115
|
+
delete globalThis.HTMLOptionElement;
|
|
116
|
+
delete globalThis.HTMLSelectElement;
|
|
117
|
+
delete globalThis.HTMLStyleElement;
|
|
118
|
+
delete globalThis.KeyboardEvent;
|
|
119
|
+
delete globalThis.localStorage;
|
|
120
|
+
delete globalThis.MutationObserver;
|
|
121
|
+
delete globalThis.navigator;
|
|
122
|
+
delete globalThis.location;
|
|
123
|
+
delete globalThis.Node;
|
|
124
|
+
delete globalThis.Option;
|
|
125
|
+
delete globalThis.requestAnimationFrame;
|
|
126
|
+
delete globalThis.ShadowRoot;
|
|
127
|
+
// Env Variables
|
|
128
|
+
delete globalThis.FORCE_HTTP;
|
|
129
|
+
delete globalThis.HTTP_CONFIG;
|
|
130
|
+
/**
|
|
131
|
+
* Cancel all timers etc.
|
|
132
|
+
*/
|
|
133
|
+
window.close();
|
|
134
|
+
});
|
|
135
|
+
exports.teardown = teardown;
|
|
136
|
+
/**
|
|
137
|
+
* Run setup on import by default.
|
|
138
|
+
*/
|
|
139
|
+
(0, exports.setup)();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configDefaults = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Default Global Configs, to be re-used (and possibly overridden) by each app
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
exports.configDefaults = {
|
|
9
|
+
globalSetup: '@genesislcap/foundation-testing/playwright',
|
|
10
|
+
projects: [
|
|
11
|
+
{
|
|
12
|
+
name: 'Chrome Stable',
|
|
13
|
+
use: {
|
|
14
|
+
browserName: 'chromium',
|
|
15
|
+
channel: 'chrome',
|
|
16
|
+
ignoreHTTPSErrors: true,
|
|
17
|
+
/**
|
|
18
|
+
* We should be able to use these 'use' blocks at the App level to provide config data to our micro frontends.
|
|
19
|
+
* This might include what backend to run against, what username password combo to use etc.
|
|
20
|
+
*
|
|
21
|
+
* config: AppLevelPkgConfig;
|
|
22
|
+
*/
|
|
23
|
+
// config: {
|
|
24
|
+
// API_HOST: 'wss://dev-foundation1/gwf/',
|
|
25
|
+
// DEFAULT_USER: process.env.DEFAULT_USER,
|
|
26
|
+
// DEFAULT_PASSWORD: process.env.DEFAULT_PASSWORD,
|
|
27
|
+
// PORT: 5030,
|
|
28
|
+
// },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
testMatch: '**/*.e2e.ts',
|
|
33
|
+
timeout: 60000,
|
|
34
|
+
webServer: {
|
|
35
|
+
command: 'npm run dev:no-open',
|
|
36
|
+
url: `http://localhost:${process.env.PORT}`,
|
|
37
|
+
timeout: 120000,
|
|
38
|
+
reuseExistingServer: !process.env.CI,
|
|
39
|
+
},
|
|
40
|
+
use: {
|
|
41
|
+
baseURL: `http://localhost:${process.env.PORT}`,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
/**
|
|
5
|
+
* @param config - A playwright configuration object.
|
|
6
|
+
* @returns A teardown function.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
* @example
|
|
10
|
+
* An example demonstrating use of environment variables and temporary folder:
|
|
11
|
+
* ```
|
|
12
|
+
* import rimraf from 'rimraf';
|
|
13
|
+
*
|
|
14
|
+
* async function setup(config: FullConfig) {
|
|
15
|
+
* process.env.FOO = 'some data';
|
|
16
|
+
* process.env.BAR = JSON.stringify({ some: 'data' });
|
|
17
|
+
* return async function teardown() {
|
|
18
|
+
* delete process.env.FOO;
|
|
19
|
+
* delete process.env.BAR;
|
|
20
|
+
* const tmpDirPath = path.join(os.tmpdir(), 'tmp-folder');
|
|
21
|
+
* rimraf(tmpDirPath, console.log);
|
|
22
|
+
* };
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function setup(config) {
|
|
27
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
return function teardown() {
|
|
29
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () { });
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
exports.default = setup;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.test = exports.defaultAuditThresholds = exports.playAudit = exports.base = exports.expect = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const test_1 = require("@playwright/test");
|
|
6
|
+
const portfinder_sync_1 = require("portfinder-sync");
|
|
7
|
+
var test_2 = require("@playwright/test");
|
|
8
|
+
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
|
|
9
|
+
var test_3 = require("@playwright/test");
|
|
10
|
+
Object.defineProperty(exports, "base", { enumerable: true, get: function () { return test_3.test; } });
|
|
11
|
+
var playwright_lighthouse_1 = require("playwright-lighthouse");
|
|
12
|
+
Object.defineProperty(exports, "playAudit", { enumerable: true, get: function () { return playwright_lighthouse_1.playAudit; } });
|
|
13
|
+
const DEFAULT_PORT = 5030;
|
|
14
|
+
const defaultPackageConfig = {
|
|
15
|
+
API_HOST: process.env.API_HOST,
|
|
16
|
+
DEFAULT_USER: process.env.DEFAULT_USER,
|
|
17
|
+
DEFAULT_PASSWORD: process.env.DEFAULT_PASSWORD,
|
|
18
|
+
PORT: Number(process.env.PORT || DEFAULT_PORT),
|
|
19
|
+
};
|
|
20
|
+
exports.defaultAuditThresholds = {
|
|
21
|
+
performance: 20,
|
|
22
|
+
accessibility: 20,
|
|
23
|
+
'best-practices': 20,
|
|
24
|
+
seo: 20,
|
|
25
|
+
pwa: 20, // 30,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Playwright test
|
|
29
|
+
* @public
|
|
30
|
+
* @remarks
|
|
31
|
+
* This is just an example playwright test fixture stub. In reality, you will likely create a custom
|
|
32
|
+
* fixture for your e2e tests depending on the needs of your app or micro frontend.
|
|
33
|
+
*/
|
|
34
|
+
exports.test = test_1.test.extend({
|
|
35
|
+
config: [defaultPackageConfig, { option: true, scope: 'worker', timeout: 60000 }],
|
|
36
|
+
port: [
|
|
37
|
+
({ config }, use) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
38
|
+
const port = (0, portfinder_sync_1.getPort)((config === null || config === void 0 ? void 0 : config.PORT) || process.env.PORT || defaultPackageConfig.PORT);
|
|
39
|
+
yield use(port);
|
|
40
|
+
}),
|
|
41
|
+
{ scope: 'worker' }, // option: true
|
|
42
|
+
],
|
|
43
|
+
browser: [
|
|
44
|
+
({ port }, use) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
const browser = yield test_1.chromium.launch({
|
|
46
|
+
args: [`--remote-debugging-port=${port}`],
|
|
47
|
+
});
|
|
48
|
+
yield use(browser);
|
|
49
|
+
}),
|
|
50
|
+
{ scope: 'worker' },
|
|
51
|
+
],
|
|
52
|
+
audit: [exports.defaultAuditThresholds, { option: true, scope: 'worker' }], // TODO: These are not being used from other test setups extending this.
|
|
53
|
+
});
|
|
54
|
+
// Perhaps we keen extending the test up the package chain... spread the mf tests into our app test
|
|
55
|
+
// const test2 = test.extend<{ foo: boolean }>({
|
|
56
|
+
// foo: [true, { option: true }],
|
|
57
|
+
// });
|
|
58
|
+
//
|
|
59
|
+
// a('lighthouse audit', async ({ foo, page }) => {
|
|
60
|
+
//
|
|
61
|
+
// });
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogicSuite = exports.createComponentSuite = exports.runCases = exports.sinon = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fast_foundation_1 = require("@microsoft/fast-foundation");
|
|
6
|
+
const component_1 = require("../component");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const uvu_1 = require("../uvu");
|
|
9
|
+
var sinon_1 = require("sinon");
|
|
10
|
+
Object.defineProperty(exports, "sinon", { enumerable: true, get: function () { return tslib_1.__importDefault(sinon_1).default; } });
|
|
11
|
+
/**
|
|
12
|
+
* Assert a set of test cases.
|
|
13
|
+
* @param fn - The function to test.
|
|
14
|
+
* @param cases - An array of test cases to run against the function.
|
|
15
|
+
* @param assertion - Optional uvu assertion method, defaults to assert.equal.
|
|
16
|
+
* @public
|
|
17
|
+
* @remarks
|
|
18
|
+
* This is available as part of the suite context, but can also be run standalone.
|
|
19
|
+
*/
|
|
20
|
+
function runCases(fn, cases, assertion = uvu_1.assert.equal) {
|
|
21
|
+
uvu_1.assert.type(fn, 'function');
|
|
22
|
+
cases.forEach(([actual, expects, msg], index) => assertion(fn(...actual), expects, msg !== null && msg !== void 0 ? msg : JSON.stringify(actual, null, 2)));
|
|
23
|
+
}
|
|
24
|
+
exports.runCases = runCases;
|
|
25
|
+
/**
|
|
26
|
+
* Create component test suite.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* Simple suite using the tag name of the component.
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { createComponentSuite } from '@genesislcap/foundation-testing';
|
|
32
|
+
* import { Component } from './component';
|
|
33
|
+
* Component; // < As we're using tag name in the Suite, we hold a reference to avoid tree shaking.
|
|
34
|
+
* const Suite = createComponentSuite<Component>('Component', 'my-component');
|
|
35
|
+
* // test cases...
|
|
36
|
+
* Suite.run();
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* Mocking a DI dependency for a composable component.
|
|
41
|
+
* ```ts
|
|
42
|
+
* const connectMock = new ConnectMock();
|
|
43
|
+
* const mocks = [Registration.instance(Connect, connectMock)];
|
|
44
|
+
* const Suite = createComponentSuite<ConnectionIndicator>('ConnectionIndicator Component', () => connectionIndicator(), null, mocks);
|
|
45
|
+
* // test cases...
|
|
46
|
+
* Suite.run();
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* An element will be required to test anything that directly or in-directly makes use of the DI container, for example,
|
|
51
|
+
* a service that can be injected into components, or has its own injected dependencies.
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { Service } from './service';
|
|
54
|
+
* @customElement({
|
|
55
|
+
* name: 'test-element',
|
|
56
|
+
* template: html`<slot></slot>`,
|
|
57
|
+
* })
|
|
58
|
+
* class TestElement extends FASTElement {}
|
|
59
|
+
* const mocks = [...];
|
|
60
|
+
* const Suite = createComponentSuite<TestElement>('Service', 'test-element', null, mocks);
|
|
61
|
+
* // test cases...
|
|
62
|
+
* Suite.run();
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* Importing the service should invoke the Service's DI registration, so in your test cases you can simply query the
|
|
66
|
+
* container to get a reference to your service.
|
|
67
|
+
* ```ts
|
|
68
|
+
* Suite('Service.x does something expected', async ({ container }) => {
|
|
69
|
+
* const myService = container.get(Service);
|
|
70
|
+
* // assert
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* You can optionally add the service to the test element for lookup convenience, but this is not required.
|
|
75
|
+
* ```ts
|
|
76
|
+
* class TestElement extends FASTElement {
|
|
77
|
+
* @Service service: Service;
|
|
78
|
+
* }
|
|
79
|
+
* Suite('Element has service injected', async ({ element }) => {
|
|
80
|
+
* assert.ok(element.service);
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @typeParam TElement - The element interface.
|
|
85
|
+
* @param title - Title of the test suite
|
|
86
|
+
* @param elementNameOrGetter - Element tag name or getter which is used to create the element within the fixture
|
|
87
|
+
* @param context - Optional component context {@link ComponentContext}
|
|
88
|
+
* @param registrations - Optional array of DI container registrations
|
|
89
|
+
* @returns The test suite
|
|
90
|
+
* @public
|
|
91
|
+
* @remarks
|
|
92
|
+
* Used to test function output given certain input arguments.
|
|
93
|
+
*/
|
|
94
|
+
function createComponentSuite(title, elementNameOrGetter, context, registrations = []) {
|
|
95
|
+
const Suite = (0, uvu_1.suite)(title, Object.assign({ element: undefined, disconnect: undefined, designSystem: undefined, container: undefined, runCases: undefined }, context));
|
|
96
|
+
Suite.before.each((c) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
try {
|
|
98
|
+
const parent = document.createElement('div');
|
|
99
|
+
const designSystem = fast_foundation_1.DesignSystem.getOrCreate(parent).register(...registrations);
|
|
100
|
+
const container = fast_foundation_1.DI.getOrCreateDOMContainer(parent);
|
|
101
|
+
const fixtureOptions = {
|
|
102
|
+
parent,
|
|
103
|
+
designSystem,
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* We might need to spread element getters for some test case, for example look at the following Fast test:
|
|
107
|
+
* packages/web-components/fast-foundation/src/picker/picker.spec.ts
|
|
108
|
+
*/
|
|
109
|
+
const { element, connect, disconnect } = yield (0, component_1.fixture)(typeof elementNameOrGetter === 'string' ? elementNameOrGetter : elementNameOrGetter(), fixtureOptions);
|
|
110
|
+
c.element = element;
|
|
111
|
+
c.disconnect = disconnect;
|
|
112
|
+
c.designSystem = designSystem;
|
|
113
|
+
c.container = container;
|
|
114
|
+
c.runCases = runCases.bind(Suite);
|
|
115
|
+
yield connect();
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
utils_1.logger.error(e);
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
122
|
+
/* eslint-disable require-atomic-updates */
|
|
123
|
+
Suite.after.each((c) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
try {
|
|
125
|
+
if (c.disconnect) {
|
|
126
|
+
yield c.disconnect();
|
|
127
|
+
}
|
|
128
|
+
c.element = undefined;
|
|
129
|
+
c.disconnect = undefined;
|
|
130
|
+
c.designSystem = undefined;
|
|
131
|
+
c.container = undefined;
|
|
132
|
+
c.runCases = undefined;
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
utils_1.logger.error(e);
|
|
136
|
+
throw e;
|
|
137
|
+
}
|
|
138
|
+
}));
|
|
139
|
+
return Suite;
|
|
140
|
+
}
|
|
141
|
+
exports.createComponentSuite = createComponentSuite;
|
|
142
|
+
/**
|
|
143
|
+
* Create logic test suite.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* import { createLogicSuite } from '@genesislcap/foundation-testing';
|
|
148
|
+
* import { myFunction } from './logic';
|
|
149
|
+
* const Suite = createLogicSuite('myFunction');
|
|
150
|
+
* Suite('myFunction should provide expected results', ({ runCases }) => {
|
|
151
|
+
* runCases(myFunction, [
|
|
152
|
+
* [['1'], true],
|
|
153
|
+
* [[123], true],
|
|
154
|
+
* [['60%'], true],
|
|
155
|
+
* [['$60'], false],
|
|
156
|
+
* [['1.1'], false],
|
|
157
|
+
* [[''], false],
|
|
158
|
+
* [[true], false],
|
|
159
|
+
* [[null], false],
|
|
160
|
+
* [[undefined], false],
|
|
161
|
+
* ]);
|
|
162
|
+
* });
|
|
163
|
+
* Suite.run();
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* @typeParam TContext - The context interface.
|
|
167
|
+
* @param title - Title of the test suite
|
|
168
|
+
* @param context - Optional context which extends {@link LogicContext}
|
|
169
|
+
* @returns The test suite
|
|
170
|
+
* @public
|
|
171
|
+
* @remarks
|
|
172
|
+
* Used to test function output given certain input arguments.
|
|
173
|
+
*/
|
|
174
|
+
function createLogicSuite(title, context) {
|
|
175
|
+
const Suite = (0, uvu_1.suite)(title, Object.assign({ runCases: (fn, cases) => {
|
|
176
|
+
uvu_1.assert.type(fn, 'function');
|
|
177
|
+
cases.forEach(([actual, expects, msg]) => uvu_1.assert.is(fn(...actual), expects, msg));
|
|
178
|
+
} }, context));
|
|
179
|
+
return Suite;
|
|
180
|
+
}
|
|
181
|
+
exports.createLogicSuite = createLogicSuite;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tsm config for apps to import and use in their tsm.js files.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```js
|
|
6
|
+
* const config = require('@genesislcap/foundation-testing/tsm');
|
|
7
|
+
*
|
|
8
|
+
* module.exports = config;
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
const config = {
|
|
14
|
+
'.css': {
|
|
15
|
+
loader: 'text',
|
|
16
|
+
},
|
|
17
|
+
'.svg': {
|
|
18
|
+
loader: 'dataurl',
|
|
19
|
+
},
|
|
20
|
+
'.jpg': {
|
|
21
|
+
loader: 'dataurl',
|
|
22
|
+
},
|
|
23
|
+
'.png': {
|
|
24
|
+
loader: 'dataurl',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
module.exports = config;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.restoreTestHarness = exports.resetTestHarness = exports.testSpy = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const sinon_1 = tslib_1.__importDefault(require("sinon"));
|
|
6
|
+
/**
|
|
7
|
+
* Predicate function used to check whether the object's value is a function
|
|
8
|
+
* we want to spy on. We want to spy on all functions, except the constructor
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
const shouldSpy = (proto) => (k) => typeof proto[k] === 'function' && k !== 'constructor';
|
|
12
|
+
/**
|
|
13
|
+
* Decorator: Used on a test harness class based on a `FoundationElement` to give it extra functionality
|
|
14
|
+
* which can be used during testing. *Important* this is to be used on a parent element compared to
|
|
15
|
+
* the element under test.
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Access the test functionality using the type with {@link WithTestHarness}
|
|
20
|
+
*
|
|
21
|
+
* Reset after each test with {@link resetTestHarness}
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
*
|
|
25
|
+
* // Testing the first function call
|
|
26
|
+
* async (\{ element \}) =\> \{
|
|
27
|
+
*
|
|
28
|
+
assert.ok(
|
|
29
|
+
element.layout.addItemFromChild.calledWith(\{
|
|
30
|
+
type: 'component',
|
|
31
|
+
componentType: 'test',
|
|
32
|
+
title: `Item test`,
|
|
33
|
+
reorderEnabled: true,
|
|
34
|
+
isClosable: false,
|
|
35
|
+
size: undefined,
|
|
36
|
+
\})
|
|
37
|
+
);
|
|
38
|
+
*
|
|
39
|
+
* // Reset the tester at the end of every test even if you don't assert on any of it!
|
|
40
|
+
* resetTestHarness(element.layout);
|
|
41
|
+
* \}
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
function testSpy(constructor) {
|
|
46
|
+
constructor['_test'] = sinon_1.default.createSandbox();
|
|
47
|
+
Object.getOwnPropertyNames(constructor.prototype)
|
|
48
|
+
.filter(shouldSpy(constructor.prototype))
|
|
49
|
+
.forEach((p) => constructor['_test'].spy(constructor.prototype, p));
|
|
50
|
+
}
|
|
51
|
+
exports.testSpy = testSpy;
|
|
52
|
+
/**
|
|
53
|
+
* Resets the history of the spied functions on the objects so previous running tests
|
|
54
|
+
* don't affect the current test.
|
|
55
|
+
* Must be called at the end of every test even if not asserting on the spies.
|
|
56
|
+
* @param wrapper - `WithTestHarness<T>` object to reset
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
function resetTestHarness(wrapper) {
|
|
60
|
+
wrapper.constructor['_test'].resetHistory();
|
|
61
|
+
}
|
|
62
|
+
exports.resetTestHarness = resetTestHarness;
|
|
63
|
+
/**
|
|
64
|
+
* Restores the spied functions back to the original functions without the spies.
|
|
65
|
+
* @param wrapper - `WithTestHarness<T>` object to restore
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
function restoreTestHarness(wrapper) {
|
|
69
|
+
wrapper.constructor['_test'].restore();
|
|
70
|
+
}
|
|
71
|
+
exports.restoreTestHarness = restoreTestHarness;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./logger"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./harness"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./promise"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logger = void 0;
|
|
4
|
+
const foundation_logger_1 = require("@genesislcap/foundation-logger");
|
|
5
|
+
/**
|
|
6
|
+
* Test logger
|
|
7
|
+
* @public
|
|
8
|
+
* @remarks
|
|
9
|
+
* Exported so you can set log levels differently across packages when needed.
|
|
10
|
+
*/
|
|
11
|
+
exports.logger = (0, foundation_logger_1.createLogger)('foundation-testing');
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.delayedResolve = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/**
|
|
6
|
+
* Delayed resolve utility.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* test('delayed resolve', async () => {
|
|
11
|
+
* const mockAPI = delayedResolve({ foo: 'bar' }, 2_000);
|
|
12
|
+
* const result = await mockAPI();
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
* @param result - The result of the promise.
|
|
16
|
+
* @param duration - An optional duration in milliseconds. Defaults to 500.
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
const delayedResolve = (result, duration = 500) => () => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
+
yield new Promise((resolve) => {
|
|
21
|
+
setTimeout(resolve, duration);
|
|
22
|
+
});
|
|
23
|
+
return result;
|
|
24
|
+
});
|
|
25
|
+
exports.delayedResolve = delayedResolve;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.timeout = exports.test = exports.suite = exports.assert = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const uvu_1 = require("uvu");
|
|
6
|
+
Object.defineProperty(exports, "suite", { enumerable: true, get: function () { return uvu_1.suite; } });
|
|
7
|
+
Object.defineProperty(exports, "test", { enumerable: true, get: function () { return uvu_1.test; } });
|
|
8
|
+
const assert = tslib_1.__importStar(require("uvu/assert"));
|
|
9
|
+
exports.assert = assert;
|
|
10
|
+
/**
|
|
11
|
+
* Timeout utility.
|
|
12
|
+
*
|
|
13
|
+
* @example A promise that never ends.
|
|
14
|
+
* ```ts
|
|
15
|
+
* test(
|
|
16
|
+
* 'should fail',
|
|
17
|
+
* timeout(async () => {
|
|
18
|
+
* await new Promise(() => {});
|
|
19
|
+
* assert.ok(true);
|
|
20
|
+
* }, 5_000),
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example A slow API.
|
|
25
|
+
* ```ts
|
|
26
|
+
* test(
|
|
27
|
+
* 'should fail',
|
|
28
|
+
* timeout(async () => {
|
|
29
|
+
* const slowMockAPI = delayedResolve({ foo: 'bar' }, 4_000);
|
|
30
|
+
* await slowMockAPI();
|
|
31
|
+
* assert.unreachable('should have timed out');
|
|
32
|
+
* }, 2_000),
|
|
33
|
+
* );
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @param callback - The async test function.
|
|
37
|
+
* @param duration - An optional duration in milliseconds. Defaults to 5_000.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
const timeout = (callback, duration = 5000) => {
|
|
41
|
+
return (context) => {
|
|
42
|
+
let timer;
|
|
43
|
+
return Promise.race([
|
|
44
|
+
callback(context),
|
|
45
|
+
new Promise((resolve, reject) => {
|
|
46
|
+
timer = setTimeout(() => reject(new Error('timed out')), duration);
|
|
47
|
+
}),
|
|
48
|
+
]).finally(() => {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
exports.timeout = timeout;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/foundation-testing",
|
|
3
3
|
"description": "Genesis Foundation Testing",
|
|
4
|
-
"version": "14.137.1
|
|
4
|
+
"version": "14.137.1",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "SEE LICENSE IN license.txt",
|
|
7
7
|
"main": "dist/esm/index.js",
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
},
|
|
17
17
|
"./e2e": {
|
|
18
18
|
"types": "./dist/dts/playwright/index.d.ts",
|
|
19
|
+
"require": "./dist/cjs/playwright/index.js",
|
|
19
20
|
"default": "./dist/esm/playwright/index.js"
|
|
20
21
|
},
|
|
21
22
|
"./jsdom": {
|
|
22
23
|
"types": "./dist/dts/jsdom/setup.d.ts",
|
|
24
|
+
"require": "./dist/cjs/jsdom/setup.js",
|
|
23
25
|
"default": "./dist/esm/jsdom/setup.js"
|
|
24
26
|
},
|
|
25
27
|
"./playwright": {
|
|
26
28
|
"types": "./dist/dts/playwright/setup.d.ts",
|
|
29
|
+
"require": "./dist/cjs/playwright/setup.js",
|
|
27
30
|
"default": "./dist/esm/playwright/setup.js"
|
|
28
31
|
},
|
|
29
32
|
"./package.json": "./package.json",
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
"scripts": {
|
|
49
52
|
"api:extract": "api-extractor run",
|
|
50
53
|
"api:document": "api-documenter markdown -i dist -o docs/api",
|
|
51
|
-
"build": "npm run clean && tsc -b ./tsconfig.json && npm run api:extract && npm run api:document",
|
|
54
|
+
"build": "npm run clean && tsc -b ./tsconfig.json && tsc -b ./tsconfig.cjs.json && npm run api:extract && npm run api:document",
|
|
52
55
|
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
53
56
|
"dev": "tsc -b ./tsconfig.json -w",
|
|
54
57
|
"test": "uvu -r tsm --tsmconfig ./src/tsm/tsm.ts -r @esbuild-kit/cjs-loader -r ./src/jsdom/setup.ts . test.ts"
|
|
@@ -63,7 +66,7 @@
|
|
|
63
66
|
"typescript": "^4.5.5"
|
|
64
67
|
},
|
|
65
68
|
"dependencies": {
|
|
66
|
-
"@genesislcap/foundation-logger": "14.137.1
|
|
69
|
+
"@genesislcap/foundation-logger": "14.137.1",
|
|
67
70
|
"@microsoft/fast-element": "^1.12.0",
|
|
68
71
|
"@microsoft/fast-foundation": "^2.49.4",
|
|
69
72
|
"@playwright/test": "^1.18.1",
|
|
@@ -84,5 +87,5 @@
|
|
|
84
87
|
"publishConfig": {
|
|
85
88
|
"access": "public"
|
|
86
89
|
},
|
|
87
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "518e1b48a6dce0a411fcdafd7cb79f9098744aff"
|
|
88
91
|
}
|