Raw merge from v2
This commit is contained in:
Родитель
9929d7d146
Коммит
ae0e8df977
|
@ -5,28 +5,31 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import { createServer } from 'http-server';
|
||||
import { createServer, } from 'http-server';
|
||||
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
|
||||
import * as testSetup from './testSetup';
|
||||
import { HttpOrHttpsServer } from './types/server';
|
||||
import { isWindows } from './testSetup';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { expect } from 'chai';
|
||||
import { killAllChrome } from '../testUtils';
|
||||
import { IAttachRequestArgs } from 'vscode-chrome-debug-core';
|
||||
import { getDebugAdapterLogFilePath } from './utils/logging';
|
||||
|
||||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
|
||||
suite('Chrome Debug Adapter etc', () => {
|
||||
let dc: ExtendedDebugClient;
|
||||
let server;
|
||||
let server: HttpOrHttpsServer | null;
|
||||
|
||||
setup(() => {
|
||||
return testSetup.setup()
|
||||
setup(function () {
|
||||
return testSetup.setup(this)
|
||||
.then(_dc => dc = _dc);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
if (server) {
|
||||
server.close();
|
||||
}
|
||||
|
||||
return testSetup.teardown();
|
||||
});
|
||||
|
||||
|
@ -43,14 +46,34 @@ suite('Chrome Debug Adapter etc', () => {
|
|||
suite('initialize', () => {
|
||||
test('should return supported features', () => {
|
||||
return dc.initializeRequest().then(response => {
|
||||
assert.equal(response.body.supportsConfigurationDoneRequest, true);
|
||||
assert.notEqual(response.body, undefined);
|
||||
assert.equal(response.body!.supportsConfigurationDoneRequest, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('launch', () => {
|
||||
test('should stop on debugger statement in file:///, sourcemaps disabled', () => {
|
||||
const testProjectRoot = path.join(DATA_ROOT, 'intervalDebugger');
|
||||
const testProjectRoot = path.join(DATA_ROOT, 'intervalDebugger');
|
||||
setup(() => {
|
||||
|
||||
server = createServer({ root: testProjectRoot });
|
||||
server.listen(7890);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
if (server) {
|
||||
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
|
||||
server = null;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* On MacOS it fails because: stopped location: path mismatch:
|
||||
* + expected: /users/vsts/agent/2.150.0/work/1/s/testdata/intervaldebugger/out/app.js
|
||||
* - actual: users/vsts/agent/2.150.0/work/1/s/testdata/intervaldebugger/out/app.js
|
||||
*/
|
||||
(isWindows ? test : test.skip)('should stop on debugger statement in file:///, sourcemaps disabled', () => {
|
||||
|
||||
const launchFile = path.join(testProjectRoot, 'index.html');
|
||||
const breakFile = path.join(testProjectRoot, 'out/app.js');
|
||||
const DEBUGGER_LINE = 2;
|
||||
|
@ -63,13 +86,9 @@ suite('Chrome Debug Adapter etc', () => {
|
|||
});
|
||||
|
||||
test('should stop on debugger statement in http://localhost', () => {
|
||||
const testProjectRoot = path.join(DATA_ROOT, 'intervalDebugger');
|
||||
const breakFile = path.join(testProjectRoot, 'src/app.ts');
|
||||
const DEBUGGER_LINE = 2;
|
||||
|
||||
server = createServer({ root: testProjectRoot });
|
||||
server.listen(7890);
|
||||
|
||||
return Promise.all([
|
||||
dc.configurationSequence(),
|
||||
dc.launch({ url: 'http://localhost:7890', webRoot: testProjectRoot }),
|
||||
|
@ -77,52 +96,94 @@ suite('Chrome Debug Adapter etc', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('Should hit breakpoint even if webRoot has unexpected case all uppercase for VisualStudio', async () => {
|
||||
const testProjectRoot = path.join(DATA_ROOT, 'breakOnLoad_javaScript');
|
||||
const breakFile = path.join(testProjectRoot, 'src/script.js');
|
||||
const DEBUGGER_LINE = 3;
|
||||
const testTitle = 'Should attach to existing instance of chrome and break on debugger statement';
|
||||
test(testTitle, async () => {
|
||||
const fullTestTitle = `Chrome Debug Adapter etc launch ${testTitle}`;
|
||||
const breakFile = path.join(testProjectRoot, 'src/app.ts');
|
||||
const DEBUGGER_LINE = 2;
|
||||
const remoteDebuggingPort = 7777;
|
||||
|
||||
const server = createServer({ root: testProjectRoot });
|
||||
const browser = await puppeteer.launch({ headless: false, args: ['http://localhost:7890', `--remote-debugging-port=${remoteDebuggingPort}`] });
|
||||
try {
|
||||
server.listen(7890);
|
||||
await dc.initializeRequest({
|
||||
adapterID: 'chrome',
|
||||
clientID: 'visualstudio',
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
pathFormat: 'path'
|
||||
});
|
||||
await dc.launchRequest({ url: 'http://localhost:7890', webRoot: testProjectRoot.toUpperCase() } as any);
|
||||
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
|
||||
await dc.configurationDoneRequest();
|
||||
await dc.assertStoppedLocation('breakpoint', { path: breakFile, line: DEBUGGER_LINE } );
|
||||
} finally {
|
||||
server.close();
|
||||
await Promise.all([
|
||||
dc.configurationSequence(),
|
||||
dc.initializeRequest().then(_ => {
|
||||
return dc.attachRequest(<IAttachRequestArgs>{
|
||||
url: 'http://localhost:7890', port: remoteDebuggingPort, webRoot: testProjectRoot,
|
||||
logFilePath: getDebugAdapterLogFilePath(fullTestTitle), logTimestamps: true
|
||||
});
|
||||
}),
|
||||
dc.assertStoppedLocation('debugger_statement', { path: breakFile, line: DEBUGGER_LINE } )
|
||||
]);
|
||||
}
|
||||
finally {
|
||||
await browser.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('Should hit breakpoint even if webRoot has unexpected case all lowercase for VisualStudio', async () => {
|
||||
const testProjectRoot = path.join(DATA_ROOT, 'breakOnLoad_javaScript');
|
||||
const breakFile = path.join(testProjectRoot, 'src/script.js');
|
||||
const DEBUGGER_LINE = 3;
|
||||
const breakFile = path.join(testProjectRoot, 'src/app.ts');
|
||||
const DEBUGGER_LINE = 2;
|
||||
|
||||
await dc.initializeRequest({
|
||||
adapterID: 'chrome',
|
||||
clientID: 'visualstudio',
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
pathFormat: 'path'
|
||||
});
|
||||
|
||||
await dc.launchRequest( { url: 'http://localhost:7890', webRoot: testProjectRoot.toLowerCase(), runtimeExecutable: puppeteer.executablePath() } as any);
|
||||
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
|
||||
await dc.configurationDoneRequest();
|
||||
await dc.assertStoppedLocation('debugger_statement', { path: breakFile, line: DEBUGGER_LINE } );
|
||||
});
|
||||
|
||||
test('Should hit breakpoint even if webRoot has unexpected case all uppercase for VisualStudio', async () => {
|
||||
const breakFile = path.join(testProjectRoot, 'src/app.ts');
|
||||
const DEBUGGER_LINE = 2;
|
||||
|
||||
await dc.initializeRequest({
|
||||
adapterID: 'chrome',
|
||||
clientID: 'visualstudio',
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
pathFormat: 'path'
|
||||
});
|
||||
await dc.launchRequest({ url: 'http://localhost:7890', webRoot: testProjectRoot.toUpperCase(), runtimeExecutable: puppeteer.executablePath() } as any);
|
||||
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
|
||||
await dc.configurationDoneRequest();
|
||||
await dc.assertStoppedLocation('debugger_statement', { path: breakFile, line: DEBUGGER_LINE } );
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* This test is baselining behvaior from V1 around what happens when the adapter tries to launch when
|
||||
* there is another running instance of chrome with --remote-debugging-port set to the same port the adapter is trying to use.
|
||||
* We expect the debug adapter to throw an exception saying that the connection attempt timed out after N milliseconds.
|
||||
* TODO: We don't think is is ideal behavior for the adapter, and want to change it fairly quickly after V2 is ready to launch.
|
||||
* right now this test exists only to verify that we match the behavior of V1
|
||||
*/
|
||||
test('Should throw error when launching if chrome debug port is in use', async () => {
|
||||
// browser already launched to the default port, and navigated away from about:blank
|
||||
const remoteDebuggingPort = 9222;
|
||||
const browser = await puppeteer.launch({ headless: false, args: ['http://localhost:7890', `--remote-debugging-port=${remoteDebuggingPort}`] });
|
||||
|
||||
const server = createServer({ root: testProjectRoot });
|
||||
try {
|
||||
server.listen(7890);
|
||||
await dc.initializeRequest({
|
||||
adapterID: 'chrome',
|
||||
clientID: 'visualstudio',
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
pathFormat: 'path'
|
||||
});
|
||||
await dc.launchRequest({ url: 'http://localhost:7890', webRoot: testProjectRoot.toLowerCase() } as any);
|
||||
await dc.setBreakpointsRequest({ source: { path: breakFile }, breakpoints: [{ line: DEBUGGER_LINE }] });
|
||||
await dc.configurationDoneRequest();
|
||||
await dc.assertStoppedLocation('breakpoint', { path: breakFile, line: DEBUGGER_LINE } );
|
||||
} finally {
|
||||
server.close();
|
||||
await Promise.all([
|
||||
dc.configurationSequence(),
|
||||
dc.launch({ url: 'http://localhost:7890', timeout: 2000, webRoot: testProjectRoot, port: remoteDebuggingPort }),
|
||||
]);
|
||||
assert.fail('Expected launch to throw a timeout exception, but it didn\'t.');
|
||||
} catch (err) {
|
||||
expect(err.message).to.satisfy( (x: string) => x.startsWith('Cannot connect to runtime process, timeout after 2000 ms'));
|
||||
}
|
||||
finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
// force kill chrome here, as it will be left open by the debug adapter (same behavior as v1)
|
||||
killAllChrome();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,20 +10,22 @@ import * as ts from 'vscode-chrome-debug-core-testsupport';
|
|||
|
||||
import * as testSetup from './testSetup';
|
||||
import { BreakOnLoadStrategy } from 'vscode-chrome-debug-core';
|
||||
import { HttpOrHttpsServer } from './types/server';
|
||||
|
||||
function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
|
||||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
|
||||
let dc: ts.ExtendedDebugClient;
|
||||
setup(() => {
|
||||
return testSetup.setup(undefined, { breakOnLoadStrategy: breakOnLoadStrategy })
|
||||
setup(function () {
|
||||
return testSetup.setup(this, undefined, { breakOnLoadStrategy: breakOnLoadStrategy })
|
||||
.then(_dc => dc = _dc);
|
||||
});
|
||||
|
||||
let server: any;
|
||||
let server: HttpOrHttpsServer | null;
|
||||
teardown(() => {
|
||||
if (server) {
|
||||
server.close();
|
||||
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
|
||||
server = null;
|
||||
}
|
||||
|
||||
return testSetup.teardown();
|
||||
|
@ -35,15 +37,16 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
|
|||
// https://github.com/Microsoft/vscode-chrome-debug-core/blob/90797bc4a3599b0a7c0f197efe10ef7fab8442fd/src/chrome/chromeDebugAdapter.ts#L692
|
||||
// so we don't want to use hitBreakpointUnverified function because it specifically checks for 'breakpoint' as the reason
|
||||
function launchWithUrlAndSetBreakpoints(url: string, projectRoot: string, scriptPath: string, lineNum: number, colNum: number): Promise<any> {
|
||||
const waitForInitialized = dc.waitForEvent('initialized');
|
||||
return Promise.all([
|
||||
dc.launch({ url: url, webRoot: projectRoot }),
|
||||
dc.waitForEvent('initialized').then(event => {
|
||||
waitForInitialized.then(_event => {
|
||||
return dc.setBreakpointsRequest({
|
||||
lines: [lineNum],
|
||||
breakpoints: [{ line: lineNum, column: colNum }],
|
||||
source: { path: scriptPath }
|
||||
});
|
||||
}).then(response => {
|
||||
}).then(_response => {
|
||||
return dc.configurationDoneRequest();
|
||||
})
|
||||
]);
|
||||
|
@ -97,7 +100,7 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
|
|||
|
||||
if (breakOnLoadStrategy === 'instrument') {
|
||||
await launchWithUrlAndSetBreakpoints(url, testProjectRoot, scriptPath, bpLine, bpCol);
|
||||
await dc.assertStoppedLocation('debugger_statement', { path: scriptPath, line: bpLine, column: bpCol });
|
||||
await dc.assertStoppedLocation('breakpoint', { path: scriptPath, line: bpLine, column: bpCol });
|
||||
} else {
|
||||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
|
||||
}
|
||||
|
@ -168,7 +171,7 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
|
|||
|
||||
if (breakOnLoadStrategy === 'instrument') {
|
||||
await launchWithUrlAndSetBreakpoints(url, testProjectRoot, scriptPath, bpLine, bpCol);
|
||||
await dc.assertStoppedLocation('debugger_statement', { path: scriptPath, line: bpLine, column: bpCol });
|
||||
await dc.assertStoppedLocation('breakpoint', { path: scriptPath, line: bpLine, column: bpCol });
|
||||
} else {
|
||||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
|
||||
}
|
||||
|
@ -205,14 +208,14 @@ function runCommonTests(breakOnLoadStrategy: BreakOnLoadStrategy) {
|
|||
|
||||
if (breakOnLoadStrategy === 'instrument') {
|
||||
await launchWithUrlAndSetBreakpoints(url, testProjectRoot, scriptPath, bpLine, bpCol);
|
||||
await dc.assertStoppedLocation('debugger_statement', { path: scriptPath, line: bpLine, column: bpCol });
|
||||
await dc.assertStoppedLocation('breakpoint', { path: scriptPath, line: bpLine, column: bpCol });
|
||||
await dc.setBreakpointsRequest({
|
||||
lines: [bpLine],
|
||||
breakpoints: [{ line: bpLine, column: bpCol }],
|
||||
source: { path: script2Path }
|
||||
});
|
||||
await dc.continueRequest();
|
||||
await dc.assertStoppedLocation('debugger_statement', { path: script2Path, line: bpLine, column: bpCol });
|
||||
await dc.assertStoppedLocation('breakpoint', { path: script2Path, line: bpLine, column: bpCol });
|
||||
} else {
|
||||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
|
||||
await dc.setBreakpointsRequest({
|
||||
|
@ -240,8 +243,8 @@ suite('BreakOnLoad', () => {
|
|||
|
||||
suite('Instrument Webpack Project', () => {
|
||||
let dc: ts.ExtendedDebugClient;
|
||||
setup(() => {
|
||||
return testSetup.setup(undefined, { breakOnLoadStrategy: 'instrument' })
|
||||
setup(function () {
|
||||
return testSetup.setup(this, undefined, { breakOnLoadStrategy: 'instrument' })
|
||||
.then(_dc => dc = _dc);
|
||||
});
|
||||
|
||||
|
@ -249,6 +252,7 @@ suite('BreakOnLoad', () => {
|
|||
teardown(() => {
|
||||
if (server) {
|
||||
server.close();
|
||||
server = null;
|
||||
}
|
||||
|
||||
return testSetup.teardown();
|
||||
|
@ -266,7 +270,7 @@ suite('BreakOnLoad', () => {
|
|||
const bpLine = 3;
|
||||
const bpCol = 1;
|
||||
|
||||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine , column: bpCol});
|
||||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
|
||||
});
|
||||
|
||||
test('Hits multiple breakpoints in a file on load', async () => {
|
||||
|
@ -307,8 +311,8 @@ suite('BreakOnLoad', () => {
|
|||
|
||||
suite('BreakOnLoad Disabled (strategy: off)', () => {
|
||||
let dc: ts.ExtendedDebugClient;
|
||||
setup(() => {
|
||||
return testSetup.setup(undefined, { breakOnLoadStrategy: 'off' })
|
||||
setup(function () {
|
||||
return testSetup.setup(this, undefined, { breakOnLoadStrategy: 'off' })
|
||||
.then(_dc => dc = _dc);
|
||||
});
|
||||
|
||||
|
@ -316,6 +320,7 @@ suite('BreakOnLoad', () => {
|
|||
teardown(() => {
|
||||
if (server) {
|
||||
server.close();
|
||||
server = null;
|
||||
}
|
||||
|
||||
return testSetup.teardown();
|
||||
|
@ -328,13 +333,11 @@ suite('BreakOnLoad', () => {
|
|||
server = createServer({ root: testProjectRoot });
|
||||
server.listen(7890);
|
||||
|
||||
const url = 'http://localhost:7890/index.html';
|
||||
|
||||
// We try to put a breakpoint at (1,1). If this doesn't get hit, the console.log statement in the script should be executed
|
||||
const bpLine = 1;
|
||||
const bpCol = 1;
|
||||
|
||||
return new Promise( (resolve, reject) => {
|
||||
return new Promise( (resolve, _reject) => {
|
||||
// Add an event listener for the output event to capture the console.log statement
|
||||
dc.addListener('output', function(event) {
|
||||
// If console.log event statement is executed, pass the test
|
||||
|
@ -343,13 +346,13 @@ suite('BreakOnLoad', () => {
|
|||
}
|
||||
}),
|
||||
Promise.all([
|
||||
dc.waitForEvent('initialized').then(event => {
|
||||
dc.waitForEvent('initialized').then(_event => {
|
||||
return dc.setBreakpointsRequest({
|
||||
lines: [bpLine],
|
||||
breakpoints: [{ line: bpLine, column: bpCol }],
|
||||
source: { path: scriptPath }
|
||||
});
|
||||
}).then(response => {
|
||||
}).then(_response => {
|
||||
return dc.configurationDoneRequest();
|
||||
}),
|
||||
|
||||
|
|
|
@ -9,23 +9,25 @@ import { createServer } from 'http-server';
|
|||
import * as ts from 'vscode-chrome-debug-core-testsupport';
|
||||
|
||||
import * as testSetup from './testSetup';
|
||||
import { HttpOrHttpsServer } from './types/server';
|
||||
|
||||
suite('Breakpoints', () => {
|
||||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
|
||||
let dc: ts.ExtendedDebugClient;
|
||||
setup(() => {
|
||||
return testSetup.setup()
|
||||
setup(function () {
|
||||
return testSetup.setup(this)
|
||||
.then(_dc => dc = _dc);
|
||||
});
|
||||
|
||||
let server: any;
|
||||
teardown(() => {
|
||||
let server: HttpOrHttpsServer | null;
|
||||
teardown(async () => {
|
||||
if (server) {
|
||||
server.close();
|
||||
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
|
||||
server = null;
|
||||
}
|
||||
|
||||
return testSetup.teardown();
|
||||
await testSetup.teardown();
|
||||
});
|
||||
|
||||
suite('Column BPs', () => {
|
||||
|
@ -39,6 +41,7 @@ suite('Breakpoints', () => {
|
|||
const url = 'http://localhost:7890/index.html';
|
||||
|
||||
const bpLine = 4;
|
||||
|
||||
const bpCol = 16;
|
||||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol });
|
||||
});
|
||||
|
@ -58,6 +61,7 @@ suite('Breakpoints', () => {
|
|||
await dc.hitBreakpointUnverified({ url, webRoot: testProjectRoot }, { path: scriptPath, line: bpLine, column: bpCol1 });
|
||||
await dc.setBreakpointsRequest({ source: { path: scriptPath }, breakpoints: [{ line: bpLine, column: bpCol2 }] });
|
||||
await dc.continueTo('breakpoint', { line: bpLine, column: bpCol2 });
|
||||
|
||||
});
|
||||
|
||||
test('BP col is adjusted to correct col', async () => {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
export function asyncMap<T, U>(array: ReadonlyArray<T>,
|
||||
callbackfn: (value: T, index: number, array: ReadonlyArray<T>) => Promise<U> | U, thisArg?: any): Promise<U[]> {
|
||||
return Promise.all(array.map(callbackfn, thisArg));
|
||||
}
|
|
@ -13,7 +13,7 @@ export class BidirectionalMap<Left, Right> {
|
|||
private readonly _rightToLeft = new ValidatedMap<Right, Left>();
|
||||
|
||||
constructor(initialContents?: Iterable<[Left, Right]> | ReadonlyArray<[Left, Right]>) {
|
||||
this._leftToRight = new ValidatedMap<Left, Right>(initialContents);
|
||||
this._leftToRight = initialContents ? new ValidatedMap<Left, Right>(initialContents) : new ValidatedMap<Left, Right>();
|
||||
const reversed = Array.from(this._leftToRight.entries()).map(e => <[Right, Left]>[e[1], e[0]]);
|
||||
this._rightToLeft = new ValidatedMap<Right, Left>(reversed);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { ValidatedMultiMap } from './validatedMultiMap';
|
||||
|
||||
export function groupByKey<T, K>(elements: T[], obtainKey: (element: T) => K): ValidatedMultiMap<K, T> {
|
||||
const grouped = ValidatedMultiMap.empty<K, T>();
|
||||
elements.forEach(element => grouped.add(obtainKey(element), element));
|
||||
return grouped;
|
||||
}
|
||||
|
||||
export function determineOrderingOfStrings(left: string, right: string): number {
|
||||
if (left < right) {
|
||||
return -1;
|
||||
} else if (left > right) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function singleElementOfArray<T>(array: ReadonlyArray<T>): T {
|
||||
if (array.length === 1) {
|
||||
return array[0];
|
||||
} else {
|
||||
throw new Error(`Expected array ${array} to have exactly a single element yet it had ${array.length}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { ValidatedMap, IValidatedMap } from './validatedMap';
|
||||
import { printMap } from './printing';
|
||||
import { ValidatedSet, IValidatedSet } from './validatedSet';
|
||||
|
||||
/** A multi map that throws exceptions instead of returning error codes. */
|
||||
export class ValidatedMultiMap<K, V> {
|
||||
|
||||
public get keysSize(): number {
|
||||
return this._wrappedMap.size;
|
||||
}
|
||||
|
||||
public get [Symbol.toStringTag](): 'Map' {
|
||||
return 'ValidatedMultiMap' as 'Map';
|
||||
}
|
||||
|
||||
private constructor(private readonly _wrappedMap: IValidatedMap<K, IValidatedSet<V>>) { }
|
||||
|
||||
public static empty<K, V>(): ValidatedMultiMap<K, V> {
|
||||
return this.usingCustomMap(new ValidatedMap<K, IValidatedSet<V>>());
|
||||
}
|
||||
|
||||
public static withContents<K, V>(initialContents: Map<K, Set<V>> | Iterable<[K, Set<V>]> | ReadonlyArray<[K, Set<V>]>): ValidatedMultiMap<K, V> {
|
||||
const elements = Array.from(initialContents).map(element => <[K, IValidatedSet<V>]>[element[0], new ValidatedSet(element[1])]);
|
||||
return this.usingCustomMap(new ValidatedMap<K, IValidatedSet<V>>(elements));
|
||||
}
|
||||
|
||||
public static usingCustomMap<K, V>(wrappedMap: IValidatedMap<K, IValidatedSet<V>>): ValidatedMultiMap<K, V> {
|
||||
return new ValidatedMultiMap(wrappedMap);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._wrappedMap.clear();
|
||||
}
|
||||
|
||||
public delete(key: K): boolean {
|
||||
return this._wrappedMap.delete(key);
|
||||
}
|
||||
|
||||
public forEach(callbackfn: (value: Set<V>, key: K, map: Map<K, Set<V>>) => void, thisArg?: any): void {
|
||||
this._wrappedMap.forEach(callbackfn, thisArg);
|
||||
}
|
||||
|
||||
public get(key: K): Set<V> {
|
||||
return this._wrappedMap.get(key);
|
||||
}
|
||||
|
||||
public getOr(key: K, elementDoesntExistAction: () => Set<V>): Set<V> {
|
||||
return this._wrappedMap.getOr(key, () => new ValidatedSet(elementDoesntExistAction()));
|
||||
}
|
||||
|
||||
public has(key: K): boolean {
|
||||
return this._wrappedMap.has(key);
|
||||
}
|
||||
|
||||
public addKeyIfNotExistant(key: K): this {
|
||||
const existingValues = this._wrappedMap.tryGetting(key);
|
||||
if (existingValues === undefined) {
|
||||
this._wrappedMap.set(key, new ValidatedSet());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public add(key: K, value: V): this {
|
||||
const existingValues = this._wrappedMap.tryGetting(key);
|
||||
if (existingValues !== undefined) {
|
||||
existingValues.add(value);
|
||||
} else {
|
||||
this._wrappedMap.set(key, new ValidatedSet([value]));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public addAndIgnoreDuplicates(key: K, value: V): this {
|
||||
const existingValues = this._wrappedMap.tryGetting(key);
|
||||
if (existingValues !== undefined) {
|
||||
existingValues.addOrReplaceIfExists(value);
|
||||
} else {
|
||||
this._wrappedMap.set(key, new ValidatedSet([value]));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public removeValueAndIfLastRemoveKey(key: K, value: V): this {
|
||||
const remainingValues = this.removeValue(key, value);
|
||||
|
||||
if (remainingValues.size === 0) {
|
||||
this._wrappedMap.delete(key);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public removeValue(key: K, value: V): Set<V> {
|
||||
const existingValues = this._wrappedMap.get(key);
|
||||
if (!existingValues.delete(value)) {
|
||||
throw new Error(`Failed to delete the value ${value} under key ${key} because it wasn't present`);
|
||||
}
|
||||
|
||||
return existingValues;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[K, Set<V>]> {
|
||||
return this._wrappedMap.entries();
|
||||
}
|
||||
|
||||
public entries(): IterableIterator<[K, Set<V>]> {
|
||||
return this._wrappedMap.entries();
|
||||
}
|
||||
|
||||
public keys(): IterableIterator<K> {
|
||||
return this._wrappedMap.keys();
|
||||
}
|
||||
|
||||
public values(): IterableIterator<Set<V>> {
|
||||
return this._wrappedMap.values();
|
||||
}
|
||||
|
||||
public tryGetting(key: K): Set<V> | undefined {
|
||||
return this._wrappedMap.tryGetting(key);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return printMap('ValidatedMultiMap', this);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,9 @@ export class ValidatedSet<K> implements IValidatedSet<K> {
|
|||
public constructor(iterable: Iterable<K>);
|
||||
public constructor(values?: ReadonlyArray<K>);
|
||||
public constructor(valuesOrIterable?: ReadonlyArray<K> | undefined | Iterable<K>) {
|
||||
this._wrappedSet = new Set(valuesOrIterable);
|
||||
this._wrappedSet = valuesOrIterable
|
||||
? new Set(valuesOrIterable)
|
||||
: new Set();
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import { printTopLevelObjectDescription } from './printObjectDescription';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
|
||||
|
@ -23,19 +24,24 @@ export class ReplacementInstruction {
|
|||
|
||||
export interface IMethodsCalledLoggerConfiguration {
|
||||
readonly replacements: ReplacementInstruction[];
|
||||
decideWhetherToWrapMethodResult(methodName: string | symbol | number, args: unknown[], result: unknown, wrapWithName: (name: string) => void): void;
|
||||
decideWhetherToWrapEventEmitterListener(receiverName: string, methodName: string | symbol | number, args: unknown[], wrapWithName: (name: string) => void): void;
|
||||
|
||||
customizeResult(methodName: string | symbol | number, args: unknown[], result: unknown): unknown;
|
||||
customizeArgumentsBeforeCall(receiverName: string, methodName: string | symbol | number, args: unknown[]): void;
|
||||
}
|
||||
|
||||
export class MethodsCalledLoggerConfiguration implements IMethodsCalledLoggerConfiguration {
|
||||
public decideWhetherToWrapMethodResult(_methodName: string | symbol | number, _args: unknown[], _result: unknown, _wrapWithName: (name: string) => void): void { }
|
||||
public decideWhetherToWrapEventEmitterListener(receiverName: string, methodName: string | symbol | number, args: unknown[], wrapWithName: (name: string) => void): void {
|
||||
if (methodName === 'on') {
|
||||
wrapWithName(`(${receiverName} emits ${args[0]})`);
|
||||
}
|
||||
|
||||
public constructor(public readonly containerName: string, private _replacements: ReplacementInstruction[]) { }
|
||||
|
||||
public customizeResult(_methodName: string | symbol | number, _args: unknown[], result: unknown): unknown {
|
||||
return result;
|
||||
}
|
||||
|
||||
public constructor(private _replacements: ReplacementInstruction[]) { }
|
||||
public customizeArgumentsBeforeCall(receiverName: string, methodName: string | symbol | number, args: object[]): void {
|
||||
if (methodName === 'on' && args.length >= 2) {
|
||||
args[1] = new MethodsCalledLogger(this, args[1], `(${receiverName} emits ${args[0]})`).wrapped();
|
||||
}
|
||||
}
|
||||
|
||||
public get replacements(): ReplacementInstruction[] {
|
||||
return this._replacements;
|
||||
|
@ -47,6 +53,7 @@ export class MethodsCalledLoggerConfiguration implements IMethodsCalledLoggerCon
|
|||
}
|
||||
|
||||
export class MethodsCalledLogger<T extends object> {
|
||||
private static _nextCallId = 10000;
|
||||
constructor(
|
||||
private readonly _configuration: IMethodsCalledLoggerConfiguration,
|
||||
private readonly _objectToWrap: T,
|
||||
|
@ -55,36 +62,38 @@ export class MethodsCalledLogger<T extends object> {
|
|||
|
||||
public wrapped(): T {
|
||||
const handler = {
|
||||
get: <K extends keyof T>(target: T, propertyKey: K, _receiver: any) => {
|
||||
get: <K extends keyof T>(target: T, propertyKey: K, receiver: any) => {
|
||||
const originalPropertyValue = target[propertyKey];
|
||||
if (typeof originalPropertyValue === 'function') {
|
||||
return (...args: any) => {
|
||||
const callId = this.generateCallId();
|
||||
try {
|
||||
if (propertyKey === 'on' && args.length >= 2) {
|
||||
let listenerPossiblyWrapped = args[1];
|
||||
this._configuration.decideWhetherToWrapEventEmitterListener(this._objectToWrapName, propertyKey, args, name => listenerPossiblyWrapped = new MethodsCalledLogger(this._configuration, args[1], name).wrapped());
|
||||
args[1] = listenerPossiblyWrapped;
|
||||
}
|
||||
|
||||
this.logCallStart(propertyKey, args, callId);
|
||||
this._configuration.customizeArgumentsBeforeCall(this._objectToWrapName, propertyKey, args);
|
||||
const result = originalPropertyValue.apply(target, args);
|
||||
if (!result || !result.then) {
|
||||
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Succesful, result);
|
||||
let resultPossiblyWrapped = result;
|
||||
this._configuration.decideWhetherToWrapMethodResult(propertyKey, args, result, name => resultPossiblyWrapped = new MethodsCalledLogger(this._configuration, result, name).wrapped());
|
||||
return resultPossiblyWrapped;
|
||||
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Succesful, result, callId);
|
||||
if (result === target) {
|
||||
return receiver;
|
||||
} else {
|
||||
return this._configuration.customizeResult(propertyKey, args, result);
|
||||
}
|
||||
} else {
|
||||
this.logSyncPartFinished(propertyKey, args, callId);
|
||||
return result.then((promiseResult: unknown) => {
|
||||
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Succesful, promiseResult);
|
||||
let resultPossiblyWrapped = promiseResult;
|
||||
this._configuration.decideWhetherToWrapMethodResult(propertyKey, args, promiseResult, name => resultPossiblyWrapped = new MethodsCalledLogger(this._configuration, <object>promiseResult, name).wrapped());
|
||||
return resultPossiblyWrapped;
|
||||
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Succesful, promiseResult, callId);
|
||||
if (promiseResult === target) {
|
||||
return receiver;
|
||||
} else {
|
||||
return this._configuration.customizeResult(propertyKey, args, promiseResult);
|
||||
}
|
||||
}, (error: unknown) => {
|
||||
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Failure, error);
|
||||
this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Failure, error, callId);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
} catch (exception) {
|
||||
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Failure, exception);
|
||||
this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Failure, exception, callId);
|
||||
throw exception;
|
||||
}
|
||||
};
|
||||
|
@ -97,6 +106,10 @@ export class MethodsCalledLogger<T extends object> {
|
|||
return new Proxy<T>(this._objectToWrap, handler);
|
||||
}
|
||||
|
||||
private generateCallId(): number {
|
||||
return MethodsCalledLogger._nextCallId++;
|
||||
}
|
||||
|
||||
private printMethodCall(propertyKey: PropertyKey, methodCallArguments: any[]): string {
|
||||
return `${this._objectToWrapName}.${String(propertyKey)}(${this.printArguments(methodCallArguments)})`;
|
||||
}
|
||||
|
@ -109,8 +122,43 @@ export class MethodsCalledLogger<T extends object> {
|
|||
return `${synchronicity === Synchronicity.Sync ? '' : ' async'}`;
|
||||
}
|
||||
|
||||
private logCall(propertyKey: PropertyKey, synchronicity: Synchronicity, methodCallArguments: any[], outcome: Outcome, resultOrException: unknown): void {
|
||||
const message = `${this.printMethodCall(propertyKey, methodCallArguments)} ${this.printMethodSynchronicity(synchronicity)} ${this.printMethodResponse(outcome, resultOrException)}`;
|
||||
/** Returns the test file and line that the code is currently executing e.g.:
|
||||
* < >
|
||||
* [22:23:28.468 UTC] START 10026: hitCountBreakpointTests.test.ts:34:2 | #incrementBtn.click()
|
||||
*/
|
||||
// TODO: Figure out how to integrate this with V2. We don't want to do this for production logging because new Error().stack is slow
|
||||
private getTestFileAndLine(): string {
|
||||
const stack = new Error().stack;
|
||||
if (stack) {
|
||||
const stackLines = stack.split('\n');
|
||||
const testCaseLine = stackLines.find(line => line.indexOf('test.ts') >= 0);
|
||||
if (testCaseLine) {
|
||||
const filenameAndLine = testCaseLine.lastIndexOf(path.sep);
|
||||
if (filenameAndLine >= 0) {
|
||||
const fileNameAndLineNumber = testCaseLine.substring(filenameAndLine + 1, testCaseLine.length - 2);
|
||||
return `${fileNameAndLineNumber} | `;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private logCallStart(propertyKey: PropertyKey, methodCallArguments: any[], callId: number): void {
|
||||
const getTestFileAndLine = this.getTestFileAndLine();
|
||||
const message = `START ${callId}: ${getTestFileAndLine}${this.printMethodCall(propertyKey, methodCallArguments)}`;
|
||||
logger.verbose(message);
|
||||
}
|
||||
|
||||
private logSyncPartFinished(propertyKey: PropertyKey, methodCallArguments: any[], callId: number): void {
|
||||
const getTestFileAndLine = this.getTestFileAndLine();
|
||||
const message = `PROMISE-RETURNED ${callId}: ${getTestFileAndLine}${this.printMethodCall(propertyKey, methodCallArguments)}`;
|
||||
logger.verbose(message);
|
||||
}
|
||||
|
||||
private logCall(propertyKey: PropertyKey, synchronicity: Synchronicity, methodCallArguments: any[], outcome: Outcome, resultOrException: unknown, callId: number): void {
|
||||
const endPrefix = callId ? `END ${callId}: ` : '';
|
||||
const message = `${endPrefix}${this.printMethodCall(propertyKey, methodCallArguments)} ${this.printMethodSynchronicity(synchronicity)} ${this.printMethodResponse(outcome, resultOrException)}`;
|
||||
logger.verbose(message);
|
||||
}
|
||||
|
||||
|
@ -130,5 +178,5 @@ export class MethodsCalledLogger<T extends object> {
|
|||
}
|
||||
|
||||
export function wrapWithMethodLogger<T extends object>(objectToWrap: T, objectToWrapName = `${objectToWrap}`): T {
|
||||
return new MethodsCalledLogger(new MethodsCalledLoggerConfiguration([]), objectToWrap, objectToWrapName).wrapped();
|
||||
return new MethodsCalledLogger(new MethodsCalledLoggerConfiguration('no container', []), objectToWrap, objectToWrapName).wrapped();
|
||||
}
|
||||
|
|
|
@ -14,16 +14,23 @@ export function printObjectDescription(objectToPrint: unknown, fallbackPrintDesc
|
|||
// This is a noice-json-rpc proxy
|
||||
printed = 'CDTP Proxy';
|
||||
} else {
|
||||
const toString = objectToPrint.toString();
|
||||
if (toString !== '[object Object]') {
|
||||
printed = toString;
|
||||
} else if (isJSONObject(objectToPrint)) {
|
||||
printed = JSON.stringify(objectToPrint);
|
||||
} else if (objectToPrint.constructor === Object) {
|
||||
printed = fallbackPrintDescription(objectToPrint);
|
||||
// This if is actually unnecesary, the previous if (!objectToPrint) { does the same thing. For some reason the typescript compiler cannot infer the type from that if
|
||||
// so we just write this code to leave the compiler happy
|
||||
// TODO: Sync with the typescript team and figure out how to remove this
|
||||
if (!objectToPrint) {
|
||||
printed = `${objectToPrint}`;
|
||||
} else {
|
||||
printed = `${objectToPrint}(${objectToPrint.constructor.name})`;
|
||||
}
|
||||
const toString = objectToPrint.toString();
|
||||
if (toString !== '[object Object]') {
|
||||
printed = toString;
|
||||
} else if (isJSONObject(objectToPrint)) {
|
||||
printed = JSON.stringify(objectToPrint);
|
||||
} else if (objectToPrint.constructor === Object) {
|
||||
printed = fallbackPrintDescription(objectToPrint);
|
||||
} else {
|
||||
printed = `${objectToPrint}(${objectToPrint.constructor.name})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof objectToPrint === 'function') {
|
||||
if (objectToPrint.name) {
|
||||
|
@ -46,7 +53,7 @@ export function printObjectDescription(objectToPrint: unknown, fallbackPrintDesc
|
|||
function isJSONObject(objectToPrint: any): boolean {
|
||||
if (objectToPrint.constructor === Object) {
|
||||
const values = _.values(objectToPrint);
|
||||
return values.every(value => value.constructor === Object);
|
||||
return values.every(value => !value || value.constructor === Object);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -16,3 +16,7 @@ export function isNotUndefined<T>(object: T | undefined): object is T {
|
|||
export interface Array<T> {
|
||||
filter<U extends T>(predicate: (element: T) => element is U): U[];
|
||||
}
|
||||
|
||||
export type Replace<T, R extends keyof T, N> = {
|
||||
[K in keyof T]: K extends R ? N : T[K];
|
||||
};
|
||||
|
|
|
@ -15,23 +15,6 @@ import { BreakpointsWizard as BreakpointsWizard } from '../wizards/breakpoints/b
|
|||
import { asyncRepeatSerially } from '../utils/repeat';
|
||||
|
||||
puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecification, (suiteContext) => {
|
||||
const reactCounterAppBaseStack = `
|
||||
ca [react-dom.production.min.js] Line 49:1
|
||||
ja [react-dom.production.min.js] Line 69:1
|
||||
ka [react-dom.production.min.js] Line 73:1
|
||||
wa [react-dom.production.min.js] Line 140:1
|
||||
Aa [react-dom.production.min.js] Line 169:6
|
||||
ya [react-dom.production.min.js] Line 158:1
|
||||
Da [react-dom.production.min.js] Line 232:1
|
||||
Ad [react-dom.production.min.js] Line 1718:1
|
||||
Gi [react-dom.production.min.js] Line 5990:1
|
||||
Kb [react-dom.production.min.js] Line 660:1
|
||||
Dd [react-dom.production.min.js] Line 1760:1
|
||||
(anonymous function) [react-dom.production.min.js] Line 6017:1
|
||||
push../node_modules/scheduler/cjs/scheduler.production.min.js.exports.unstable_runWithPriority [scheduler.production.min.js] Line 274:1
|
||||
Ii [react-dom.production.min.js] Line 6016:1
|
||||
Cd [react-dom.production.min.js] Line 1737:1`;
|
||||
|
||||
puppeteerTest("Hit count breakpoint = 3 pauses on the button's 3rd click", suiteContext, async (_context, page) => {
|
||||
const incBtn = await page.waitForSelector('#incrementBtn');
|
||||
|
||||
|
@ -39,20 +22,18 @@ puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecificatio
|
|||
const counterBreakpoints = breakpoints.at('Counter.jsx');
|
||||
|
||||
const setStateBreakpoint = await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'this.setState({ count: newval });',
|
||||
text: 'this.setState({ count: newval });',
|
||||
boundText: 'setState({ count: newval })',
|
||||
hitCountCondition: '% 3'
|
||||
});
|
||||
|
||||
await asyncRepeatSerially(2, () => incBtn.click());
|
||||
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 17:12
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
|
||||
await incBtn.click();
|
||||
|
||||
await breakpoints.assertNotPaused();
|
||||
await breakpoints.waitAndAssertNoMoreEvents();
|
||||
|
||||
await setStateBreakpoint.unset();
|
||||
});
|
||||
|
@ -64,40 +45,32 @@ puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecificatio
|
|||
const counterBreakpoints = breakpoints.at('Counter.jsx');
|
||||
|
||||
const setStateBreakpoint = await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'this.setState({ count: newval });',
|
||||
text: 'this.setState({ count: newval })',
|
||||
boundText: 'setState({ count: newval })',
|
||||
hitCountCondition: '= 3'
|
||||
});
|
||||
|
||||
const setNewValBreakpoint = await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'const newval = this.state.count + 1',
|
||||
text: 'const newval = this.state.count + 1',
|
||||
boundText: 'state.count + 1',
|
||||
hitCountCondition: '= 5'
|
||||
});
|
||||
|
||||
const stepInBreakpoint = await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'this.stepIn();',
|
||||
text: 'this.stepIn()',
|
||||
boundText: 'stepIn()',
|
||||
hitCountCondition: '= 4'
|
||||
});
|
||||
|
||||
await asyncRepeatSerially(2, () => incBtn.click());
|
||||
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 17:12
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
|
||||
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 18:12
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
|
||||
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 16:27
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
|
||||
await incBtn.click();
|
||||
|
||||
await breakpoints.assertNotPaused();
|
||||
await breakpoints.waitAndAssertNoMoreEvents();
|
||||
|
||||
await setStateBreakpoint.unset();
|
||||
await setNewValBreakpoint.unset();
|
||||
|
@ -112,41 +85,33 @@ puppeteerSuite('Hit count breakpoints on a React project', reactTestSpecificatio
|
|||
|
||||
const { setStateBreakpoint, stepInBreakpoint, setNewValBreakpoint } = await counterBreakpoints.batch(async () => ({
|
||||
setStateBreakpoint: await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'this.setState({ count: newval });',
|
||||
text: 'this.setState({ count: newval });',
|
||||
boundText: 'setState({ count: newval })',
|
||||
hitCountCondition: '= 3'
|
||||
}),
|
||||
|
||||
setNewValBreakpoint: await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'const newval = this.state.count + 1',
|
||||
text: 'const newval = this.state.count + 1',
|
||||
boundText: 'state.count + 1',
|
||||
hitCountCondition: '= 5'
|
||||
}),
|
||||
|
||||
stepInBreakpoint: await counterBreakpoints.hitCountBreakpoint({
|
||||
lineText: 'this.stepIn();',
|
||||
text: 'this.stepIn();',
|
||||
boundText: 'stepIn()',
|
||||
hitCountCondition: '= 4'
|
||||
})
|
||||
}));
|
||||
|
||||
await asyncRepeatSerially(2, () => incBtn.click());
|
||||
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 17:12
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
|
||||
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 18:12
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
|
||||
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), `
|
||||
increment [Counter.jsx] Line 16:27
|
||||
onClick [Counter.jsx] Line 30:60
|
||||
${reactCounterAppBaseStack}`);
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
await stepInBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
await setNewValBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click());
|
||||
|
||||
await incBtn.click();
|
||||
|
||||
await breakpoints.assertNotPaused();
|
||||
await breakpoints.waitAndAssertNoMoreEvents();
|
||||
|
||||
await counterBreakpoints.batch(async () => {
|
||||
await setStateBreakpoint.unset();
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Hit count breakpoints' scenarios
|
||||
* Hit count breakpoint syntax: (>|>=|=|<|<=|%)?\s*([0-9]+)
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { puppeteerSuite, puppeteerTest } from '../puppeteer/puppeteerSuite';
|
||||
import { reactWithLoopTestSpecification } from '../resources/resourceProjects';
|
||||
import { BreakpointsWizard as BreakpointsWizard } from '../wizards/breakpoints/breakpointsWizard';
|
||||
import { expect } from 'chai';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
|
||||
puppeteerSuite('Hit count breakpoints combinations', reactWithLoopTestSpecification, (suiteContext) => {
|
||||
interface IConditionConfiguration {
|
||||
condition: string; // The condition for the hit count breakpoint
|
||||
iterationsExpectedToPause: number[]; // In which iteration numbers it should pause (e.g.: 1st, 5th, 12th, etc...)
|
||||
noMorePausesAfterwards: boolean;
|
||||
}
|
||||
|
||||
// * Hit count breakpoint syntax: (>|>=|=|<|<=|%)?\s*([0-9]+)
|
||||
const manyConditionsConfigurations: IConditionConfiguration[] = [
|
||||
{ condition: '= 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
|
||||
{ condition: '= 1', iterationsExpectedToPause: [1], noMorePausesAfterwards: true },
|
||||
{ condition: '= 2', iterationsExpectedToPause: [2], noMorePausesAfterwards: true },
|
||||
{ condition: '= 12', iterationsExpectedToPause: [12], noMorePausesAfterwards: true },
|
||||
{ condition: '> 0', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '> 1', iterationsExpectedToPause: [2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '>\t2', iterationsExpectedToPause: [3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '> 187', iterationsExpectedToPause: [188, 189, 190, 191], noMorePausesAfterwards: false },
|
||||
{ condition: '>= 0', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '>= 1', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '>= 2', iterationsExpectedToPause: [2, 3, 4, 5, 6, 7, 8, 9, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '>= 37', iterationsExpectedToPause: [37, 38, 39], noMorePausesAfterwards: false },
|
||||
{ condition: '< 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
|
||||
{ condition: '< \t \t 1', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
|
||||
{ condition: '< 2', iterationsExpectedToPause: [1], noMorePausesAfterwards: true },
|
||||
{ condition: '< \t13', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], noMorePausesAfterwards: true },
|
||||
{ condition: '<=\t 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
|
||||
{ condition: '<= 1', iterationsExpectedToPause: [1], noMorePausesAfterwards: true },
|
||||
{ condition: '<= 15', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], noMorePausesAfterwards: true },
|
||||
{ condition: '% 0', iterationsExpectedToPause: [], noMorePausesAfterwards: true },
|
||||
{ condition: '% 1', iterationsExpectedToPause: [1, 2, 3, 4, 5, 6], noMorePausesAfterwards: false },
|
||||
{ condition: '% 2', iterationsExpectedToPause: [2, 4, 6, 8, 10], noMorePausesAfterwards: false },
|
||||
{ condition: '%\t3', iterationsExpectedToPause: [3, 6, 9, 12, 15], noMorePausesAfterwards: false },
|
||||
{ condition: '% \t \t \t 12', iterationsExpectedToPause: [12, 24, 36, 48, 60], noMorePausesAfterwards: false },
|
||||
{ condition: '%\t\t\t17', iterationsExpectedToPause: [17, 34, 51, 68], noMorePausesAfterwards: false },
|
||||
{ condition: '% 37', iterationsExpectedToPause: [37, 74, 111, 148], noMorePausesAfterwards: false },
|
||||
];
|
||||
|
||||
manyConditionsConfigurations.forEach(conditionConfiguration => {
|
||||
puppeteerTest(`condition ${conditionConfiguration.condition}`, suiteContext, async (_context, page) => {
|
||||
const incBtn = await page.waitForSelector('#incrementBtn');
|
||||
const breakpoints = BreakpointsWizard.create(suiteContext.debugClient, reactWithLoopTestSpecification);
|
||||
const counterBreakpoints = breakpoints.at('Counter.jsx');
|
||||
|
||||
const setStateBreakpoint = await counterBreakpoints.hitCountBreakpoint({
|
||||
text: 'iterationNumber * iterationNumber',
|
||||
hitCountCondition: conditionConfiguration.condition
|
||||
});
|
||||
|
||||
const buttonClicked = incBtn.click();
|
||||
|
||||
for (const nextIterationToPause of conditionConfiguration.iterationsExpectedToPause) {
|
||||
/**
|
||||
* The iterationNumber variable counts in the js-debuggee code how many times the loop was executed. We verify
|
||||
* the value of this variable to validate that a bp with = 12 paused on the 12th iteration rather than on the 1st one
|
||||
* (The breakpoint is located in the same place in both iterations, so we need to use state to differenciate between those two cases)
|
||||
*/
|
||||
await setStateBreakpoint.assertIsHitThenResume({ variables: { local_contains: { iterationNumber: nextIterationToPause } } });
|
||||
}
|
||||
|
||||
logger.log(`No more pauses afterwards = ${conditionConfiguration.noMorePausesAfterwards}`);
|
||||
if (conditionConfiguration.noMorePausesAfterwards) {
|
||||
await breakpoints.waitAndAssertNoMoreEvents();
|
||||
await setStateBreakpoint.unset();
|
||||
} else {
|
||||
await breakpoints.waitAndConsumePausedEvent(setStateBreakpoint);
|
||||
await setStateBreakpoint.unset();
|
||||
await breakpoints.resume();
|
||||
}
|
||||
|
||||
await buttonClicked;
|
||||
});
|
||||
});
|
||||
|
||||
// * Hit count breakpoint syntax: (>|>=|=|<|<=|%)?\s*([0-9]+)
|
||||
const manyInvalidConditions: string[] = [
|
||||
'== 3',
|
||||
'= -1',
|
||||
'> -200',
|
||||
'< -24',
|
||||
'< 64\t',
|
||||
'< 5 ',
|
||||
'>= -95',
|
||||
'<= -5',
|
||||
'\t= 1',
|
||||
'< = 4',
|
||||
' <= 4',
|
||||
'% -200',
|
||||
'stop always',
|
||||
' = 3 ',
|
||||
'= 1 + 1',
|
||||
'> 3.5',
|
||||
];
|
||||
|
||||
manyInvalidConditions.forEach(invalidCondition => {
|
||||
puppeteerTest(`invalid condition ${invalidCondition}`, suiteContext, async () => {
|
||||
const breakpoints = BreakpointsWizard.create(suiteContext.debugClient, reactWithLoopTestSpecification);
|
||||
const counterBreakpoints = breakpoints.at('Counter.jsx');
|
||||
|
||||
try {
|
||||
await counterBreakpoints.hitCountBreakpoint({
|
||||
text: 'iterationNumber * iterationNumber',
|
||||
hitCountCondition: invalidCondition
|
||||
});
|
||||
} catch (exception) {
|
||||
expect(exception.toString()).to.be.equal(`Error: [debugger-for-chrome] Error processing "setBreakpoints": Didn't recognize <${invalidCondition}> as a valid hit count condition`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
import * as testSetup from '../testSetup';
|
||||
import { puppeteerSuite } from '../puppeteer/puppeteerSuite';
|
||||
import { TestProjectSpec } from '../framework/frameworkTestSupport';
|
||||
import { FrameworkTestSuite } from '../framework/frameworkCommonTests';
|
||||
import * as path from 'path';
|
||||
import { utils } from 'vscode-chrome-debug-core';
|
||||
|
||||
const SINGLE_INLINE_TEST_SPEC = TestProjectSpec.fromTestPath('inline_scripts', '', utils.pathToFileURL(path.join(testSetup.DATA_ROOT, 'inline_scripts/single.html')));
|
||||
const MULTIPLE_INLINE_TEST_SPEC = TestProjectSpec.fromTestPath('inline_scripts', '', utils.pathToFileURL(path.join(testSetup.DATA_ROOT, 'inline_scripts/multiple.html')));
|
||||
|
||||
suite('Inline Script Tests', () => {
|
||||
puppeteerSuite('Single inline script', SINGLE_INLINE_TEST_SPEC, (suiteContext) => {
|
||||
const frameworkTests = new FrameworkTestSuite('Simple JS', suiteContext);
|
||||
frameworkTests.testBreakpointHitsOnPageAction('Should stop on a breakpoint in an in-line script', '#actionButton', 'single.html', 'a + b;', page => page.click('#actionButton') );
|
||||
});
|
||||
|
||||
puppeteerSuite.skip('Multiple inline scripts', MULTIPLE_INLINE_TEST_SPEC, (suiteContext) => {
|
||||
const frameworkTests = new FrameworkTestSuite('Simple JS', suiteContext);
|
||||
frameworkTests.testBreakpointHitsOnPageAction('Should stop on a breakpoint in multiple in-line scripts (Skipped, not currently working in V2)',
|
||||
'#actionButton', 'multiple.html', 'inlineScript1', page => page.click('#actionButton') );
|
||||
});
|
||||
});
|
|
@ -0,0 +1,289 @@
|
|||
import { TestProjectSpec } from '../framework/frameworkTestSupport';
|
||||
import { VariablesWizard } from '../wizards/variables/variablesWizard';
|
||||
import { LaunchProject } from '../fixtures/launchProject';
|
||||
import { testUsing } from '../fixtures/testUsing';
|
||||
|
||||
// Scopes' kinds: 'global' | 'local' | 'with' | 'closure' | 'catch' | 'block' | 'script' | 'eval' | 'module'
|
||||
// TODO: Test several scopes at the same time. They can be repeated, and the order does matter
|
||||
suite('Variables scopes', function () {
|
||||
testUsing('local', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/localScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
local: `
|
||||
this = Window (Object)
|
||||
arguments = Arguments(0) [] (Object)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
consoleDotLog = function consoleDotLog(m) { … } (Function)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a\nstring with\nnewlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`}
|
||||
);
|
||||
});
|
||||
|
||||
testUsing('globals', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/globalScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertNewGlobalVariariablesAre(async () => {
|
||||
await launchProject.pausedWizard.resume();
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
},
|
||||
// The variables declared with const, and let aren't global variables so they won't appear here
|
||||
`
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
consoleDotLog = function consoleDotLog(m) { … } (Function)
|
||||
e = Error: hi (Object)
|
||||
element = p {align: "", title: "", lang: "", …} (Object)
|
||||
evalVar1 = 16 (number)
|
||||
evalVar2 = "sdlfk" (string)
|
||||
evalVar3 = Array(3) [1, 2, 3] (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
i = 101 (number)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a\nstring with\nnewlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object) // TODO: This and other types seems wrong. Investigate
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`);
|
||||
});
|
||||
|
||||
testUsing('script', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/scriptScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
script: `
|
||||
this = Window (Object)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a\nstring with\nnewlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`}
|
||||
);
|
||||
});
|
||||
|
||||
testUsing('block', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/blockScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre(
|
||||
{
|
||||
block: `
|
||||
this = Window (Object)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
consoleDotLog = function consoleDotLog(m) { … } (Function)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a\nstring with\nnewlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
testUsing('catch', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/catchScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
catch: `
|
||||
exception = Error: Something went wrong (Object)`}
|
||||
);
|
||||
});
|
||||
|
||||
testUsing('closure', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/closureScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
closure: `
|
||||
arguments = Arguments(0) [] (Object)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
consoleDotLog = function consoleDotLog(m) { … } (Function)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a\nstring with\nnewlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
pauseInside = function pauseInside() { … } (Function)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`}
|
||||
);
|
||||
});
|
||||
|
||||
testUsing('eval', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/evalScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
eval: `
|
||||
this = Window (Object)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a\nstring with\nnewlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`}
|
||||
);
|
||||
});
|
||||
|
||||
testUsing('with', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/withScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
with: `
|
||||
this = Window (Object)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
consoleDotLog = function (m) { … } (Function)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
evalVar1 = 16 (number)
|
||||
evalVar2 = "sdlfk" (string)
|
||||
evalVar3 = Array(3) [1, 2, 3] (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function () { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
i = 101 (number)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a
|
||||
string with
|
||||
newlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)
|
||||
__proto__ = Object {constructor: , __defineGetter__: , __defineSetter__: , …} (Object)`
|
||||
});
|
||||
});
|
||||
|
||||
testUsing('module', context => LaunchProject.create(context, TestProjectSpec.fromTestPath('variablesScopes/moduleScope')), async (launchProject) => {
|
||||
await launchProject.pausedWizard.waitUntilPausedOnDebuggerStatement();
|
||||
|
||||
await new VariablesWizard(launchProject.debugClient).assertTopFrameVariablesAre({
|
||||
module: `
|
||||
this = undefined (undefined)
|
||||
b = body {text: "", link: "", vLink: "", …} (Object)
|
||||
bool = true (boolean)
|
||||
buffer = ArrayBuffer(8) {} (Object)
|
||||
buffView = Int32Array(2) [234, 0] (Object)
|
||||
consoleDotLog = function consoleDotLog(m2) { … } (Function)
|
||||
e = Error: hi (Object)
|
||||
element = body {text: "", link: "", vLink: "", …} (Object)
|
||||
fn = () => { … } (Function)
|
||||
fn2 = function (param) { … } (Function)
|
||||
globalCode = "page loaded" (string)
|
||||
inf = Infinity (number)
|
||||
infStr = "Infinity" (string)
|
||||
longStr = "this is a
|
||||
string with
|
||||
newlines" (string)
|
||||
m = Map(1) {} (Object)
|
||||
manyPropsObj = Object {0: 1, 1: 3, 2: 5, …} (Object)
|
||||
myVar = Object {num: 1, str: "Global", obj: Object, …} (Object)
|
||||
nan = NaN (number)
|
||||
obj = Object {a: 2, thing: <accessor>} (Object)
|
||||
qqq = undefined (undefined)
|
||||
r = /^asdf.*$/g {lastIndex: 0} (Object)
|
||||
s = Symbol(hi) (symbol)
|
||||
str = "hello" (string)
|
||||
xyz = 4 (number)`
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { IFixture } from './fixture';
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import * as testSetup from '../testSetup';
|
||||
import { IBeforeAndAfterContext, ITestCallbackContext } from 'mocha';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
|
||||
/**
|
||||
* Default set up for all our tests. We expect all our tests to need to do this setup
|
||||
* which includes configure the debug adapter, logging, etc...
|
||||
*/
|
||||
export class DefaultFixture implements IFixture {
|
||||
private constructor(public readonly debugClient: ExtendedDebugClient) {
|
||||
// Running tests on CI can time out at the default 5s, so we up this to 15s
|
||||
debugClient.defaultTimeout = 15000;
|
||||
}
|
||||
|
||||
/** Create a new fixture using the provided setup context */
|
||||
public static async create(context: IBeforeAndAfterContext | ITestCallbackContext): Promise<DefaultFixture> {
|
||||
return new DefaultFixture(await testSetup.setup(context));
|
||||
}
|
||||
|
||||
/** Create a new fixture using the full title of the test case currently running */
|
||||
public static async createWithTitle(testTitle: string): Promise<DefaultFixture> {
|
||||
return new DefaultFixture(await testSetup.setupWithTitle(testTitle));
|
||||
}
|
||||
|
||||
public async cleanUp(): Promise<void> {
|
||||
logger.log(`Default test clean-up`);
|
||||
await testSetup.teardown();
|
||||
logger.log(`Default test clean-up finished`);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `DefaultFixture`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { PromiseOrNot } from 'vscode-chrome-debug-core';
|
||||
|
||||
/**
|
||||
* See https://en.wikipedia.org/wiki/Test_fixture for more context
|
||||
*/
|
||||
|
||||
/**
|
||||
* A fixture represents a particular piece of set up of the context, or the environment or
|
||||
* the configuration needed for a test or suite to run.
|
||||
* The fixture should make those changes during it's constructor or static constructor method,
|
||||
* and it'll "clean up" those changes with the cleanUp method
|
||||
*/
|
||||
export interface IFixture {
|
||||
/** Clean-up the context, or changes made by the fixture */
|
||||
cleanUp(): PromiseOrNot<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fixture representing that no setup is needed
|
||||
*/
|
||||
export class NullFixture implements IFixture {
|
||||
public cleanUp(): void { }
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { TestProjectSpec } from '../framework/frameworkTestSupport';
|
||||
import { IFixture } from './fixture';
|
||||
import { DefaultFixture } from './defaultFixture';
|
||||
import { LaunchWebServer, ProvideStaticUrl } from './launchWebServer';
|
||||
import { LaunchPuppeteer } from '../puppeteer/launchPuppeteer';
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { Page, Browser } from 'puppeteer';
|
||||
import { ITestCallbackContext, IBeforeAndAfterContext } from 'mocha';
|
||||
import { URL } from 'url';
|
||||
import { PausedWizard } from '../wizards/pausedWizard';
|
||||
|
||||
/** Perform all the steps neccesary to launch a particular project such as:
|
||||
* - Default fixture/setup
|
||||
* - Launch web-server
|
||||
* - Connect puppeteer to Chrome
|
||||
*/
|
||||
export class LaunchProject implements IFixture {
|
||||
private constructor(
|
||||
private readonly _defaultFixture: DefaultFixture,
|
||||
private readonly _launchWebServer: LaunchWebServer | ProvideStaticUrl,
|
||||
public readonly pausedWizard: PausedWizard,
|
||||
private readonly _launchPuppeteer: LaunchPuppeteer) { }
|
||||
|
||||
public static async create(testContext: IBeforeAndAfterContext | ITestCallbackContext, testSpec: TestProjectSpec): Promise<LaunchProject> {
|
||||
|
||||
const launchWebServer = (testSpec.staticUrl) ?
|
||||
new ProvideStaticUrl(new URL(testSpec.staticUrl), testSpec) :
|
||||
await LaunchWebServer.launch(testSpec);
|
||||
|
||||
const defaultFixture = await DefaultFixture.create(testContext);
|
||||
|
||||
// We need to create the PausedWizard before launching the debuggee to listen to all events and avoid race conditions
|
||||
const pausedWizard = PausedWizard.forClient(defaultFixture.debugClient);
|
||||
|
||||
const launchPuppeteer = await LaunchPuppeteer.create(defaultFixture.debugClient, launchWebServer.launchConfig);
|
||||
return new LaunchProject(defaultFixture, launchWebServer, pausedWizard, launchPuppeteer);
|
||||
}
|
||||
|
||||
/** Client for the debug adapter being used for this test */
|
||||
public get debugClient(): ExtendedDebugClient {
|
||||
return this._defaultFixture.debugClient;
|
||||
}
|
||||
|
||||
/** Object to control the debugged browser via puppeteer */
|
||||
public get browser(): Browser {
|
||||
return this._launchPuppeteer.browser;
|
||||
}
|
||||
|
||||
/** Object to control the debugged page via puppeteer */
|
||||
public get page(): Page {
|
||||
return this._launchPuppeteer.page;
|
||||
}
|
||||
|
||||
public get url(): URL {
|
||||
return this._launchWebServer.url;
|
||||
}
|
||||
|
||||
public async cleanUp(): Promise<void> {
|
||||
await this.pausedWizard.waitAndAssertNoMoreEvents();
|
||||
await this._defaultFixture.cleanUp(); // Disconnect the debug-adapter first
|
||||
await this._launchPuppeteer.cleanUp(); // Then disconnect puppeteer and close chrome
|
||||
await this._launchWebServer.cleanUp(); // Finally disconnect the web-server
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { createServer } from 'http-server';
|
||||
import { IFixture } from './fixture';
|
||||
import { TestProjectSpec } from '../framework/frameworkTestSupport';
|
||||
import { HttpOrHttpsServer } from '../types/server';
|
||||
import { ILaunchRequestArgs } from 'vscode-chrome-debug-core';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
import { URL } from 'url';
|
||||
|
||||
async function createServerAsync(root: string): Promise<HttpOrHttpsServer> {
|
||||
const server = createServer({ root });
|
||||
return await new Promise((resolve, reject) => {
|
||||
logger.log(`About to launch web-server`);
|
||||
server.listen(0, '127.0.0.1', function (this: HttpOrHttpsServer, error?: any) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(this); // We return the this pointer which is the internal server object, which has access to the .address() method
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function closeServer(server: HttpOrHttpsServer): Promise<void> {
|
||||
logger.log(`Closing web-server`);
|
||||
await new Promise((resolve, reject) => {
|
||||
server.close((error?: any) => {
|
||||
if (error) {
|
||||
logger.log('Error closing server in teardown: ' + (error && error.message));
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
logger.log(`Web-server closed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a web-server for the test project listening on the default port
|
||||
*/
|
||||
export class LaunchWebServer implements IFixture {
|
||||
private constructor(private readonly _server: HttpOrHttpsServer, private readonly _testSpec: TestProjectSpec) { }
|
||||
|
||||
public static async launch(testSpec: TestProjectSpec): Promise<LaunchWebServer> {
|
||||
return new LaunchWebServer(await createServerAsync(testSpec.props.webRoot), testSpec);
|
||||
}
|
||||
|
||||
public get url(): URL {
|
||||
const address = this._server.address();
|
||||
return new URL(`http://localhost:${address.port}/`);
|
||||
}
|
||||
|
||||
public get launchConfig(): ILaunchRequestArgs {
|
||||
return Object.assign({}, this._testSpec.props.launchConfig, { url: this.url.toString() });
|
||||
}
|
||||
|
||||
public get port(): number {
|
||||
return this._server.address().port;
|
||||
}
|
||||
|
||||
public async cleanUp(): Promise<void> {
|
||||
await closeServer(this._server);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `LaunchWebServer`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProvideStaticUrl implements IFixture {
|
||||
|
||||
public constructor(public readonly url: URL, private readonly testSpec: TestProjectSpec) {}
|
||||
|
||||
public get launchConfig(): ILaunchRequestArgs {
|
||||
return {...this.testSpec.props.launchConfig, url: this.url.href };
|
||||
}
|
||||
cleanUp() {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { IFixture } from './fixture';
|
||||
import { asyncMap } from '../core-v2/chrome/collections/async';
|
||||
|
||||
/** Combine multiple fixtures into a single fixture, for easier management (e.g. you just need to call a single cleanUp method) */
|
||||
export class MultipleFixtures implements IFixture {
|
||||
private readonly _fixtures: IFixture[];
|
||||
|
||||
public constructor(...fixtures: IFixture[]) {
|
||||
this._fixtures = fixtures;
|
||||
}
|
||||
|
||||
public async cleanUp(): Promise<void> {
|
||||
await asyncMap(this._fixtures, fixture => fixture.cleanUp());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { IFixture } from './fixture';
|
||||
import { PromiseOrNot } from 'vscode-chrome-debug-core';
|
||||
import { ITestCallbackContext } from 'mocha';
|
||||
|
||||
/** Run a test doing the setup/cleanup indicated by the provided fixtures */
|
||||
function testUsingFunction<T extends IFixture>(
|
||||
expectation: string,
|
||||
fixtureProvider: (context: ITestCallbackContext) => PromiseOrNot<T>,
|
||||
testFunction: (fixtures: T) => Promise<void>): Mocha.ITest {
|
||||
return test(expectation, async function () {
|
||||
const fixture = await fixtureProvider(this);
|
||||
try {
|
||||
await testFunction(fixture);
|
||||
} finally {
|
||||
await fixture.cleanUp();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const testUsing = testUsingFunction;
|
|
@ -11,20 +11,22 @@ import * as path from 'path';
|
|||
import * as testSetup from '../testSetup';
|
||||
import { setBreakpoint, setConditionalBreakpoint } from '../intTestSupport';
|
||||
import { puppeteerSuite, puppeteerTest } from '../puppeteer/puppeteerSuite';
|
||||
import { FrameworkTestSuite } from './frameworkCommonTests';
|
||||
import { FrameworkTestSuite, testBreakOnLoad } from './frameworkCommonTests';
|
||||
import { TestProjectSpec } from './frameworkTestSupport';
|
||||
|
||||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
const REACT_PROJECT_ROOT = path.join(DATA_ROOT, 'react', 'dist');
|
||||
const TEST_SPEC = new TestProjectSpec( { projectRoot: REACT_PROJECT_ROOT } );
|
||||
|
||||
// This test doesn't use puppeteer, so we leave it outside the suite
|
||||
testBreakOnLoad('React', TEST_SPEC, 'react_App_render');
|
||||
|
||||
puppeteerSuite('React Framework Tests', TEST_SPEC, (suiteContext) => {
|
||||
|
||||
suite('Common Framework Tests', () => {
|
||||
const frameworkTests = new FrameworkTestSuite('React', suiteContext);
|
||||
frameworkTests.testPageReloadBreakpoint('react_App_render');
|
||||
frameworkTests.testPauseExecution();
|
||||
frameworkTests.testBreakOnLoad('react_App_render');
|
||||
frameworkTests.testStepOver('react_Counter_increment');
|
||||
frameworkTests.testStepOut('react_Counter_increment', 'react_Counter_stepOut');
|
||||
frameworkTests.testStepIn('react_Counter_stepInStop', 'react_Counter_stepIn');
|
||||
|
@ -33,17 +35,23 @@ puppeteerSuite('React Framework Tests', TEST_SPEC, (suiteContext) => {
|
|||
suite('React specific tests', () => {
|
||||
|
||||
puppeteerTest('Should hit breakpoint in .jsx file', suiteContext, async (_context, page) => {
|
||||
const pausedWizard = suiteContext.launchProject!.pausedWizard;
|
||||
|
||||
const location = suiteContext.breakpointLabels.get('react_Counter_increment');
|
||||
const incBtn = await page.waitForSelector('#incrementBtn');
|
||||
|
||||
await setBreakpoint(suiteContext.debugClient, location);
|
||||
const clicked = incBtn.click();
|
||||
await suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
|
||||
await suiteContext.debugClient.continueRequest();
|
||||
await pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
await pausedWizard.resume();
|
||||
await clicked;
|
||||
});
|
||||
|
||||
puppeteerTest('Should hit conditional breakpoint in .jsx file', suiteContext, async (_context, page) => {
|
||||
const pausedWizard = suiteContext.launchProject!.pausedWizard;
|
||||
|
||||
const location = suiteContext.breakpointLabels.get('react_Counter_increment');
|
||||
const incBtn = await page.waitForSelector('#incrementBtn');
|
||||
|
||||
|
@ -54,9 +62,11 @@ puppeteerSuite('React Framework Tests', TEST_SPEC, (suiteContext) => {
|
|||
// don't await the last click, as the stopped debugger will deadlock it
|
||||
const clicked = incBtn.click();
|
||||
await suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
|
||||
await pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
// Be sure to await the continue request, otherwise sometimes the last click promise will
|
||||
// be rejected because the chrome instance is closed before it completes.
|
||||
await suiteContext.debugClient.continueRequest();
|
||||
await pausedWizard.resume();
|
||||
await clicked;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import { BreakpointLocation } from '../intTestSupport';
|
||||
import { loadProjectLabels } from '../labels';
|
||||
import { expect } from 'chai';
|
||||
import * as path from 'path';
|
||||
|
||||
suite('Test framework tests', () => {
|
||||
test('Should correctly find breakpoint labels in test source files', async () => {
|
||||
let labels = await loadProjectLabels('./testdata');
|
||||
let worldLabel = labels.get('WorldLabel');
|
||||
|
||||
expect(worldLabel).to.eql(<BreakpointLocation>{ path: 'testdata\\labelTest.ts', line: 9 });
|
||||
expect(worldLabel.path).to.eql(path.join('testdata', 'labelTest.ts'));
|
||||
expect(worldLabel.line).to.eql(9);
|
||||
});
|
||||
|
||||
test('Should correctly find block comment breakpoint labels in test source files', async () => {
|
||||
let labels = await loadProjectLabels('./testdata');
|
||||
let blockLabel = labels.get('blockLabel');
|
||||
|
||||
expect(blockLabel).to.eql(<BreakpointLocation>{ path: 'testdata\\labelTest.ts', line: 10 });
|
||||
expect(blockLabel.path).to.eql(path.join('testdata', 'labelTest.ts'));
|
||||
expect(blockLabel.line).to.eql(10);
|
||||
});
|
||||
});
|
|
@ -3,9 +3,16 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { puppeteerTest } from '../puppeteer/puppeteerSuite';
|
||||
import { puppeteerTest, PuppeteerTestContext } from '../puppeteer/puppeteerSuite';
|
||||
import { setBreakpoint } from '../intTestSupport';
|
||||
import { FrameworkTestContext } from './frameworkTestSupport';
|
||||
import { LaunchWebServer } from '../fixtures/launchWebServer';
|
||||
import { loadProjectLabels } from '../labels';
|
||||
import { TestProjectSpec } from './frameworkTestSupport';
|
||||
import { DefaultFixture } from '../fixtures/defaultFixture';
|
||||
import { MultipleFixtures } from '../fixtures/multipleFixtures';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { PausedWizard } from '../wizards/pausedWizard';
|
||||
import { BreakpointsWizard } from '../wizards/breakpoints/breakpointsWizard';
|
||||
|
||||
/**
|
||||
* A common framework test suite that allows for easy (one-liner) testing of various
|
||||
|
@ -15,20 +22,11 @@ import { FrameworkTestContext } from './frameworkTestSupport';
|
|||
export class FrameworkTestSuite {
|
||||
constructor(
|
||||
private frameworkName: string,
|
||||
private suiteContext: FrameworkTestContext
|
||||
private suiteContext: PuppeteerTestContext
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Test that we can stop on a breakpoint set before launch
|
||||
* @param bpLabel Label for the breakpoint to set
|
||||
*/
|
||||
testBreakOnLoad(bpLabel: string) {
|
||||
return test(`${this.frameworkName} - Should stop on breakpoint on initial page load`, async () => {
|
||||
const testSpec = this.suiteContext.testSpec;
|
||||
const location = this.suiteContext.breakpointLabels.get(bpLabel);
|
||||
await this.suiteContext.debugClient
|
||||
.hitBreakpointUnverified(testSpec.props.launchConfig, location);
|
||||
});
|
||||
private get pausedWizard(): PausedWizard {
|
||||
return this.suiteContext.launchProject!.pausedWizard;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,8 +44,13 @@ export class FrameworkTestSuite {
|
|||
|
||||
await setBreakpoint(debugClient, bpLocation);
|
||||
const reloaded = page.reload();
|
||||
|
||||
await debugClient.assertStoppedLocation('breakpoint', bpLocation);
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
await debugClient.continueRequest();
|
||||
await this.pausedWizard.waitAndConsumeResumedEvent();
|
||||
|
||||
await reloaded;
|
||||
});
|
||||
}
|
||||
|
@ -68,10 +71,16 @@ export class FrameworkTestSuite {
|
|||
await setBreakpoint(this.suiteContext.debugClient, location);
|
||||
const clicked = incBtn.click();
|
||||
await this.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
const stopOnStep = this.suiteContext.debugClient.assertStoppedLocation('step', stepInLocation);
|
||||
await this.suiteContext.debugClient.stepInAndStop();
|
||||
await this.pausedWizard.waitAndConsumeResumedEvent();
|
||||
|
||||
await stopOnStep;
|
||||
await this.suiteContext.debugClient.continueRequest();
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
await this.pausedWizard.resume();
|
||||
await clicked;
|
||||
});
|
||||
}
|
||||
|
@ -90,10 +99,16 @@ export class FrameworkTestSuite {
|
|||
await setBreakpoint(this.suiteContext.debugClient, location);
|
||||
const clicked = incBtn.click();
|
||||
await this.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
const stopOnStep = this.suiteContext.debugClient.assertStoppedLocation('step', { ...location, line: location.line + 1 });
|
||||
await this.suiteContext.debugClient.nextAndStop();
|
||||
await this.pausedWizard.waitAndConsumeResumedEvent();
|
||||
|
||||
await stopOnStep;
|
||||
await this.suiteContext.debugClient.continueRequest();
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
await this.pausedWizard.resume();
|
||||
await clicked;
|
||||
});
|
||||
}
|
||||
|
@ -113,10 +128,16 @@ export class FrameworkTestSuite {
|
|||
await setBreakpoint(this.suiteContext.debugClient, location);
|
||||
const clicked = incBtn.click();
|
||||
await this.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
const stopOnStep = this.suiteContext.debugClient.assertStoppedLocation('step', stepOutLocation);
|
||||
await this.suiteContext.debugClient.stepOutAndStop();
|
||||
await this.pausedWizard.waitAndConsumeResumedEvent();
|
||||
|
||||
await stopOnStep;
|
||||
await this.suiteContext.debugClient.continueRequest();
|
||||
await this.pausedWizard.waitAndConsumePausedEvent(() => {});
|
||||
|
||||
await this.pausedWizard.resume();
|
||||
await clicked;
|
||||
});
|
||||
}
|
||||
|
@ -126,11 +147,56 @@ export class FrameworkTestSuite {
|
|||
* @param bpLocation
|
||||
*/
|
||||
testPauseExecution() {
|
||||
return puppeteerTest(`${this.frameworkName} - Should correctly pause execution on a pause request`, this.suiteContext, async (context, _page) => {
|
||||
const debugClient = context.debugClient;
|
||||
await debugClient.pauseRequest({ threadId: 0 });
|
||||
await debugClient.waitForEvent('stopped');
|
||||
await debugClient.continueRequest();
|
||||
return puppeteerTest(`${this.frameworkName} - Should correctly pause execution on a pause request`, this.suiteContext, async (_context, _page) => {
|
||||
await this.pausedWizard.pause();
|
||||
|
||||
// TODO: Verify we are actually pausing in the expected line
|
||||
|
||||
await this.pausedWizard.resume();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic breakpoint test. This can be used for many different types of breakpoint tests with the following structure:
|
||||
*
|
||||
* 1. Wait for the page to load by waiting for the selector: `waitSelectorId`
|
||||
* 2. Set a breakpoint at `bpLabel`
|
||||
* 3. Execute a trigger event that should cause the breakpoint to be hit using the function `trigger`
|
||||
* 4. Assert that the breakpoint is hit on the expected location, and continue
|
||||
*
|
||||
* @param waitSelector an html selector to identify a resource to wait for for page load
|
||||
* @param bpLabel
|
||||
* @param trigger
|
||||
*/
|
||||
testBreakpointHitsOnPageAction(description: string, waitSelector: string, file: string, bpLabel: string, trigger: (page: puppeteer.Page) => Promise<void>) {
|
||||
return puppeteerTest(`${this.frameworkName} - ${description}`, this.suiteContext, async (context, page) => {
|
||||
await page.waitForSelector(`${waitSelector}`);
|
||||
const breakpoints = BreakpointsWizard.create(context.debugClient, context.testSpec);
|
||||
const breakpointWizard = breakpoints.at(file);
|
||||
const bp = await breakpointWizard.breakpoint({ text: bpLabel });
|
||||
await bp.assertIsHitThenResumeWhen(() => trigger(page));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we can stop on a breakpoint set before launch
|
||||
* @param bpLabel Label for the breakpoint to set
|
||||
*/
|
||||
export function testBreakOnLoad(frameworkName: string, testSpec: TestProjectSpec, bpLabel: string) {
|
||||
const testTitle = `${frameworkName} - Should stop on breakpoint on initial page load`;
|
||||
return test(testTitle, async () => {
|
||||
const defaultFixture = await DefaultFixture.createWithTitle(testTitle);
|
||||
const launchWebServer = await LaunchWebServer.launch(testSpec);
|
||||
const fixture = new MultipleFixtures(launchWebServer, defaultFixture);
|
||||
|
||||
try {
|
||||
const breakpointLabels = await loadProjectLabels(testSpec.props.webRoot);
|
||||
const location = breakpointLabels.get(bpLabel);
|
||||
await defaultFixture.debugClient
|
||||
.hitBreakpointUnverified(launchWebServer.launchConfig, location);
|
||||
} finally {
|
||||
await fixture.cleanUp();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { BreakpointLocation } from '../intTestSupport';
|
||||
import { ILaunchRequestArgs } from '../../../src/chromeDebugInterfaces';
|
||||
import { IValidatedMap } from '../core-v2/chrome/collections/validatedMap';
|
||||
import { DATA_ROOT } from '../testSetup';
|
||||
|
||||
/*
|
||||
* A collection of supporting classes/functions for running framework tests
|
||||
|
@ -20,13 +23,9 @@ export interface ProjectSpecProps {
|
|||
/** The directory from which to host the project for a test */
|
||||
webRoot?: string;
|
||||
/** The outfiles directory for the test project */
|
||||
outFiles?: string;
|
||||
outFiles?: string[];
|
||||
/** The default launch configuration for the test project */
|
||||
launchConfig?: ILaunchRequestArgs;
|
||||
/** Port to use for the server */
|
||||
port?: number;
|
||||
/** Url to use for the project */
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,30 +33,44 @@ export interface ProjectSpecProps {
|
|||
* attached to in order to test the debug adapter)
|
||||
*/
|
||||
export class TestProjectSpec {
|
||||
|
||||
_props: ProjectSpecProps;
|
||||
_props: Required<ProjectSpecProps>;
|
||||
get props() { return this._props; }
|
||||
|
||||
/**
|
||||
* @param props Parameters for the project spec. The only required param is "projectRoot", others will be set to sensible defaults
|
||||
*/
|
||||
constructor(props: ProjectSpecProps) {
|
||||
this._props = props;
|
||||
this._props.projectSrc = props.projectSrc || path.join(props.projectRoot, 'src');
|
||||
this._props.webRoot = props.webRoot || props.projectRoot;
|
||||
this._props.outFiles = props.outFiles || path.join(props.projectRoot, 'out');
|
||||
this._props.port = props.port || 7890;
|
||||
this._props.url = props.url || `http://localhost:${props.port}/`;
|
||||
this._props.launchConfig = props.launchConfig || {
|
||||
url: props.url,
|
||||
outFiles: [props.outFiles],
|
||||
sourceMaps: true,
|
||||
/* TODO: get this dynamically */
|
||||
runtimeExecutable: 'node_modules/puppeteer/.local-chromium/win64-637110/chrome-win/chrome.exe',
|
||||
webRoot: props.webRoot
|
||||
constructor(props: ProjectSpecProps, public readonly staticUrl?: string) {
|
||||
const outFiles = props.outFiles || [path.join(props.projectRoot, 'out')];
|
||||
const webRoot = props.webRoot || props.projectRoot;
|
||||
this._props = {
|
||||
projectRoot: props.projectRoot,
|
||||
projectSrc: props.projectSrc || path.join(props.projectRoot, 'src'),
|
||||
webRoot: webRoot,
|
||||
outFiles: outFiles,
|
||||
launchConfig: props.launchConfig || {
|
||||
outFiles: outFiles,
|
||||
sourceMaps: true,
|
||||
runtimeExecutable: puppeteer.executablePath(),
|
||||
webRoot: webRoot
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify project by it's location relative to the testdata folder e.g.:
|
||||
* - TestProjectSpec.fromTestPath('react_with_loop/dist')
|
||||
* - TestProjectSpec.fromTestPath('simple')
|
||||
*
|
||||
* The path *can only* use forward-slahes "/" as separators
|
||||
*/
|
||||
public static fromTestPath(reversedSlashesRelativePath: string, sourceDir = 'src', staticUrl?: string): TestProjectSpec {
|
||||
const pathComponents = reversedSlashesRelativePath.split('/');
|
||||
const projectAbsolutePath = path.join(...[DATA_ROOT].concat(pathComponents));
|
||||
const projectSrc = path.join(projectAbsolutePath, sourceDir);
|
||||
let props: ProjectSpecProps = { projectRoot: projectAbsolutePath, projectSrc };
|
||||
return new TestProjectSpec(props, staticUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to a source file
|
||||
* @param filename
|
||||
|
@ -72,9 +85,48 @@ export class TestProjectSpec {
|
|||
*/
|
||||
export interface FrameworkTestContext {
|
||||
/** The test project specs for the currently executing test suite */
|
||||
testSpec: TestProjectSpec;
|
||||
readonly testSpec: TestProjectSpec;
|
||||
/** A mapping of labels set in source files to a breakpoint location for a test */
|
||||
breakpointLabels?: Map<string, BreakpointLocation>;
|
||||
readonly breakpointLabels: IValidatedMap<string, BreakpointLocation>;
|
||||
/** The debug adapter test support client */
|
||||
debugClient?: ExtendedDebugClient;
|
||||
readonly debugClient: ExtendedDebugClient;
|
||||
}
|
||||
|
||||
export class ReassignableFrameworkTestContext implements FrameworkTestContext {
|
||||
private _wrapped: FrameworkTestContext = new NotInitializedFrameworkTestContext();
|
||||
|
||||
public get testSpec(): TestProjectSpec {
|
||||
return this._wrapped.testSpec;
|
||||
}
|
||||
|
||||
public get breakpointLabels(): IValidatedMap<string, BreakpointLocation> {
|
||||
return this._wrapped.breakpointLabels;
|
||||
}
|
||||
|
||||
public get debugClient(): ExtendedDebugClient {
|
||||
return this._wrapped.debugClient;
|
||||
}
|
||||
|
||||
public reassignTo(newWrapped: FrameworkTestContext): this {
|
||||
this._wrapped = newWrapped;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotInitializedFrameworkTestContext implements FrameworkTestContext {
|
||||
public get testSpec(): TestProjectSpec {
|
||||
return this.throwNotInitializedException();
|
||||
}
|
||||
|
||||
public get breakpointLabels(): IValidatedMap<string, BreakpointLocation> {
|
||||
return this.throwNotInitializedException();
|
||||
}
|
||||
|
||||
public get debugClient(): ExtendedDebugClient {
|
||||
return this.throwNotInitializedException();
|
||||
}
|
||||
|
||||
private throwNotInitializedException(): never {
|
||||
throw new Error(`This test context hasn't been initialized yet. This is probably a bug in the tests`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,26 @@
|
|||
|
||||
import { DebugClient } from 'vscode-debugadapter-testsupport';
|
||||
|
||||
const ImplementsBreakpointLocation = Symbol();
|
||||
/**
|
||||
* Simple breakpoint location params (based on what the debug test client accepts)
|
||||
*/
|
||||
export interface BreakpointLocation {
|
||||
/** The path to the source file in which to set a breakpoint */
|
||||
path: string;
|
||||
/** The line number in the file to set a breakpoint on */
|
||||
line: number;
|
||||
/** Optional breakpoint column */
|
||||
column?: number;
|
||||
/** Whether or not we should assert if the bp is verified or not */
|
||||
verified?: boolean;
|
||||
export class BreakpointLocation {
|
||||
[ImplementsBreakpointLocation]: 'BreakpointLocation';
|
||||
|
||||
public constructor (
|
||||
/** The path to the source file in which to set a breakpoint */
|
||||
public readonly path: string,
|
||||
/** The line number in the file to set a breakpoint on */
|
||||
public readonly line: number,
|
||||
/** Optional breakpoint column */
|
||||
public readonly column?: number,
|
||||
/** Whether or not we should assert if the bp is verified or not */
|
||||
public readonly verified?: boolean) {}
|
||||
|
||||
public toString(): string {
|
||||
return `${this.path}:${this.line}:${this.column} verified: ${this.verified}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ import * as fs from 'fs';
|
|||
import * as util from 'util';
|
||||
import * as readline from 'readline';
|
||||
import * as path from 'path';
|
||||
import { ValidatedMap, IValidatedMap } from './core-v2/chrome/collections/validatedMap';
|
||||
|
||||
/*
|
||||
* Contains classes and functions to find and use test breakpoint labels in test project files
|
||||
|
@ -16,7 +17,7 @@ import * as path from 'path';
|
|||
const readdirAsync = util.promisify(fs.readdir);
|
||||
|
||||
const labelRegex = /(\/\/|\/\*)\s*bpLabel:\s*(.+?)\b/;
|
||||
const ignoreList = [ 'node_modules', '.git' ];
|
||||
const ignoreList = [ 'node_modules', '.git', path.join('dist', 'out'), path.join('testdata', 'react', 'src') ];
|
||||
|
||||
/**
|
||||
* A label in a source file that tells us where to put a breakpoint for a specific test
|
||||
|
@ -30,15 +31,15 @@ export interface BreakpointLabel {
|
|||
* Load all breakpoint labels that exist in the 'projectRoot' directory
|
||||
* @param projectRoot Root directory for the test project
|
||||
*/
|
||||
export async function loadProjectLabels(projectRoot: string): Promise<Map<string, BreakpointLocation>> {
|
||||
export async function loadProjectLabels(projectRoot: string): Promise<IValidatedMap<string, BreakpointLocation>> {
|
||||
|
||||
const labelMap = new Map<string, BreakpointLocation>();
|
||||
const labelMap = new ValidatedMap<string, BreakpointLocation>();
|
||||
if (containsIgnores(projectRoot)) return labelMap;
|
||||
|
||||
const files = await readdirAsync(projectRoot);
|
||||
|
||||
for (let file of files) {
|
||||
let subMap: Map<string, BreakpointLocation> = null;
|
||||
let subMap: Map<string, BreakpointLocation> | null = null;
|
||||
const fullPath = path.join(projectRoot, file);
|
||||
if (fs.lstatSync(fullPath).isDirectory()) {
|
||||
subMap = await loadProjectLabels(fullPath);
|
||||
|
@ -71,7 +72,7 @@ export async function loadLabelsFromFile(filePath: string): Promise<Map<string,
|
|||
let match = labelRegex.exec(fileLine);
|
||||
|
||||
if (match) {
|
||||
labelMap.set(match[2], { path: filePath, line: lineNumber });
|
||||
labelMap.set(match[2], new BreakpointLocation(filePath, lineNumber));
|
||||
}
|
||||
lineNumber++;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as getPort from 'get-port';
|
||||
import { IFixture } from '../fixtures/fixture';
|
||||
import { launchTestAdapter } from '../intTestSupport';
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { connectPuppeteer, getPageByUrl } from './puppeteerSupport';
|
||||
import { logCallsTo } from '../utils/logging';
|
||||
import { isThisV1 } from '../testSetup';
|
||||
import { Browser, Page } from 'puppeteer';
|
||||
import { ILaunchRequestArgs } from 'vscode-chrome-debug-core';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
|
||||
/**
|
||||
* Launch the debug adapter using the Puppeteer version of chrome, and then connect to it
|
||||
*
|
||||
* The fixture offers access to both the browser, and page objects of puppeteer
|
||||
*/
|
||||
export class LaunchPuppeteer implements IFixture {
|
||||
public constructor(public readonly browser: Browser, public readonly page: Page) { }
|
||||
|
||||
public static async create(debugClient: ExtendedDebugClient, launchConfig: ILaunchRequestArgs): Promise<LaunchPuppeteer> {
|
||||
const daPort = await getPort();
|
||||
logger.log(`About to launch debug-adapter at port: ${daPort}`);
|
||||
await launchTestAdapter(debugClient, Object.assign({}, launchConfig, { port: daPort }));
|
||||
const browser = await connectPuppeteer(daPort);
|
||||
|
||||
const page = logCallsTo(await getPageByUrl(browser, launchConfig.url!), 'PuppeteerPage');
|
||||
|
||||
// This short wait appears to be necessary to completely avoid a race condition in V1 (tried several other
|
||||
// strategies to wait deterministically for all scripts to be loaded and parsed, but have been unsuccessful so far)
|
||||
// If we don't wait here, there's always a possibility that we can send the set breakpoint request
|
||||
// for a subsequent test after the scripts have started being parsed/run by Chrome, yet before
|
||||
// the target script is parsed, in which case the adapter will try to use breakOnLoad, but
|
||||
// the instrumentation BP will never be hit, leaving our breakpoint in limbo
|
||||
if (isThisV1) {
|
||||
await new Promise(a => setTimeout(a, 500));
|
||||
}
|
||||
|
||||
return new LaunchPuppeteer(browser, page);
|
||||
}
|
||||
|
||||
public async cleanUp(): Promise<void> {
|
||||
logger.log(`Closing puppeteer and chrome`);
|
||||
try {
|
||||
await this.browser.close();
|
||||
logger.log(`Scucesfully closed puppeteer and chrome`);
|
||||
} catch (exception) {
|
||||
logger.log(`Failed to close puppeteer: ${exception}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public toString(): string {
|
||||
return `LaunchPuppeteer`;
|
||||
}
|
||||
}
|
|
@ -3,23 +3,55 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createServer } from 'http-server';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import * as testSetup from '../testSetup';
|
||||
import { launchTestAdapter } from '../intTestSupport';
|
||||
import { getPageByUrl, connectPuppeteer } from './puppeteerSupport';
|
||||
import { FrameworkTestContext, TestProjectSpec } from '../framework/frameworkTestSupport';
|
||||
import { FrameworkTestContext, TestProjectSpec, ReassignableFrameworkTestContext } from '../framework/frameworkTestSupport';
|
||||
import { loadProjectLabels } from '../labels';
|
||||
import { isThisV2, isThisV1 } from '../testSetup';
|
||||
import { ISuiteCallbackContext, ISuite } from 'mocha';
|
||||
import { NullFixture } from '../fixtures/fixture';
|
||||
import { LaunchProject } from '../fixtures/launchProject';
|
||||
import { PromiseOrNot } from 'vscode-chrome-debug-core';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
import { setTestLogName } from '../utils/logging';
|
||||
|
||||
/**
|
||||
* Extends the normal debug adapter context to include context relevant to puppeteer tests.
|
||||
*/
|
||||
export interface PuppeteerTestContext extends FrameworkTestContext {
|
||||
export interface IPuppeteerTestContext extends FrameworkTestContext {
|
||||
/** The connected puppeteer browser object */
|
||||
browser: puppeteer.Browser;
|
||||
browser: puppeteer.Browser | null;
|
||||
/** The currently running html page in Chrome */
|
||||
page: puppeteer.Page;
|
||||
page: puppeteer.Page | null;
|
||||
launchProject: LaunchProject | null;
|
||||
}
|
||||
|
||||
export class PuppeteerTestContext extends ReassignableFrameworkTestContext {
|
||||
private _browser: puppeteer.Browser | null = null;
|
||||
private _page: puppeteer.Page | null = null;
|
||||
private _launchProject: LaunchProject | null = null;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public get browser(): puppeteer.Browser | null {
|
||||
return this._browser;
|
||||
}
|
||||
|
||||
public get page(): puppeteer.Page | null {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public get launchProject(): LaunchProject | null {
|
||||
return this._launchProject;
|
||||
}
|
||||
|
||||
public reassignTo(newWrapped: IPuppeteerTestContext): this {
|
||||
super.reassignTo(newWrapped);
|
||||
this._page = newWrapped.page;
|
||||
this._browser = newWrapped.browser;
|
||||
this._launchProject = newWrapped.launchProject;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,31 +62,31 @@ export interface PuppeteerTestContext extends FrameworkTestContext {
|
|||
* @param context The test context for this test sutie
|
||||
* @param testFunction The inner test function that will run a test using puppeteer
|
||||
*/
|
||||
export async function puppeteerTest(
|
||||
function puppeteerTestFunction(
|
||||
description: string,
|
||||
context: FrameworkTestContext,
|
||||
testFunction: (context: PuppeteerTestContext, page: puppeteer.Page) => Promise<any>
|
||||
) {
|
||||
return test(description, async function () {
|
||||
const debugClient = await context.debugClient;
|
||||
await launchTestAdapter(debugClient, context.testSpec.props.launchConfig);
|
||||
const browser = await connectPuppeteer(9222);
|
||||
const page = await getPageByUrl(browser, context.testSpec.props.url);
|
||||
|
||||
// This short wait appears to be necessary to completely avoid a race condition in V1 (tried several other
|
||||
// strategies to wait deterministically for all scripts to be loaded and parsed, but have been unsuccessful so far)
|
||||
// If we don't wait here, there's always a possibility that we can send the set breakpoint request
|
||||
// for a subsequent test after the scripts have started being parsed/run by Chrome, yet before
|
||||
// the target script is parsed, in which case the adapter will try to use breakOnLoad, but
|
||||
// the instrumentation BP will never be hit, leaving our breakpoint in limbo
|
||||
if (isThisV1) {
|
||||
await new Promise(a => setTimeout(a, 500));
|
||||
}
|
||||
|
||||
await testFunction({ ...context, browser, page }, page);
|
||||
context: PuppeteerTestContext,
|
||||
testFunction: (context: PuppeteerTestContext, page: puppeteer.Page) => PromiseOrNot<void>,
|
||||
functionToDeclareTest: (description: string, callback: (this: ISuiteCallbackContext) => void) => ISuite = test
|
||||
): void {
|
||||
functionToDeclareTest(description, function () {
|
||||
return testFunction(context, context.page!);
|
||||
});
|
||||
}
|
||||
|
||||
puppeteerTestFunction.skip = (
|
||||
description: string,
|
||||
_context: PuppeteerTestContext,
|
||||
_testFunction: (context: IPuppeteerTestContext, page: puppeteer.Page) => Promise<any>
|
||||
) => test.skip(description, () => { throw new Error(`We don't expect this to be called`); });
|
||||
|
||||
puppeteerTestFunction.only = (
|
||||
description: string,
|
||||
context: PuppeteerTestContext,
|
||||
testFunction: (context: IPuppeteerTestContext, page: puppeteer.Page) => Promise<any>
|
||||
) => puppeteerTestFunction(description, context, testFunction, test.only);
|
||||
|
||||
export const puppeteerTest = puppeteerTestFunction;
|
||||
|
||||
/**
|
||||
* Defines a custom test suite which will:
|
||||
* 1) automatically launch a server from a test project directory,
|
||||
|
@ -67,40 +99,50 @@ export async function puppeteerTest(
|
|||
* @param testSpec Info about the test project on which this suite will be based
|
||||
* @param callback The inner test suite that uses this context
|
||||
*/
|
||||
export function puppeteerSuite(
|
||||
function puppeteerSuiteFunction(
|
||||
description: string,
|
||||
testSpec: TestProjectSpec,
|
||||
callback: (suiteContext: FrameworkTestContext) => any
|
||||
): Mocha.ISuite {
|
||||
return suite(description, () => {
|
||||
let suiteContext: FrameworkTestContext = { testSpec };
|
||||
callback: (suiteContext: PuppeteerTestContext) => void,
|
||||
suiteFunctionToUse: (description: string, callback: (this: ISuiteCallbackContext) => void) => ISuite | void = suite
|
||||
): Mocha.ISuite | void {
|
||||
return suiteFunctionToUse(description, () => {
|
||||
let testContext = new PuppeteerTestContext();
|
||||
let fixture: LaunchProject | NullFixture = new NullFixture(); // This variable is shared across all test of this suite
|
||||
|
||||
let server: any;
|
||||
setup(async function () {
|
||||
setTestLogName(this.currentTest.fullTitle());
|
||||
const breakpointLabels = await loadProjectLabels(testSpec.props.webRoot);
|
||||
const launchProject = fixture = await LaunchProject.create(this, testSpec);
|
||||
|
||||
setup(async () => {
|
||||
const portNumber = process.env['MSFT_TEST_DA_PORT']
|
||||
? parseInt(process.env['MSFT_TEST_DA_PORT'], 10)
|
||||
: undefined;
|
||||
|
||||
let debugClient = await testSetup.setup(portNumber);
|
||||
suiteContext.debugClient = debugClient;
|
||||
await suiteContext.debugClient;
|
||||
|
||||
// Running tests on CI can time out at the default 5s, so we up this to 10s
|
||||
suiteContext.breakpointLabels = await loadProjectLabels(testSpec.props.webRoot);
|
||||
suiteContext.debugClient.defaultTimeout = 15000;
|
||||
|
||||
server = createServer({ root: testSpec.props.webRoot });
|
||||
server.listen(7890);
|
||||
testContext.reassignTo({
|
||||
testSpec, debugClient: launchProject.debugClient, breakpointLabels, browser: launchProject.browser, page: launchProject.page, launchProject
|
||||
});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
if (server) {
|
||||
server.close();
|
||||
}
|
||||
return testSetup.teardown();
|
||||
teardown(async () => {
|
||||
await fixture.cleanUp();
|
||||
fixture = new NullFixture();
|
||||
logger.log(`teardown finished`);
|
||||
});
|
||||
|
||||
callback(suiteContext);
|
||||
callback(testContext);
|
||||
});
|
||||
}
|
||||
|
||||
puppeteerSuiteFunction.skip = (
|
||||
description: string,
|
||||
testSpec: TestProjectSpec,
|
||||
callback: (suiteContext: PuppeteerTestContext) => any
|
||||
) => puppeteerSuiteFunction(description, testSpec, callback, suite.skip);
|
||||
|
||||
puppeteerSuiteFunction.only = (
|
||||
description: string,
|
||||
testSpec: TestProjectSpec,
|
||||
callback: (suiteContext: PuppeteerTestContext) => any,
|
||||
) => puppeteerSuiteFunction(description, testSpec, callback, suite.only);
|
||||
puppeteerSuiteFunction.skip = (
|
||||
description: string,
|
||||
_testSpec: TestProjectSpec,
|
||||
_callback: (suiteContext: PuppeteerTestContext) => any,
|
||||
) => suite.skip(description, () => {});
|
||||
export const puppeteerSuite = puppeteerSuiteFunction;
|
|
@ -7,8 +7,7 @@
|
|||
* Functions that make puppeteer testing easier
|
||||
*/
|
||||
|
||||
import * as util from 'util';
|
||||
import * as request from 'request';
|
||||
import * as request from 'request-promise-native';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
|
||||
/**
|
||||
|
@ -17,8 +16,8 @@ import * as puppeteer from 'puppeteer';
|
|||
*/
|
||||
export async function connectPuppeteer(port: number): Promise<puppeteer.Browser> {
|
||||
|
||||
const resp = await util.promisify(request)(`http://localhost:${port}/json/version`);
|
||||
const { webSocketDebuggerUrl } = JSON.parse(resp.body);
|
||||
const resp = await request(`http://localhost:${port}/json/version`);
|
||||
const { webSocketDebuggerUrl } = JSON.parse(resp);
|
||||
|
||||
const browser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl, defaultViewport: null });
|
||||
return browser;
|
||||
|
@ -47,7 +46,7 @@ export async function getPageByUrl(browser: puppeteer.Browser, url: string, time
|
|||
while ((current - before) < timeout) {
|
||||
|
||||
const pages = await browser.pages();
|
||||
const desiredPage = pages.find(p => p.url() === url);
|
||||
const desiredPage = pages.find(p => p.url().toLowerCase() === url.toLowerCase());
|
||||
if (desiredPage) {
|
||||
return desiredPage;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// Needed to make @types/puppeteer pass type checking
|
||||
// See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24419
|
||||
|
||||
interface Element { }
|
|
@ -5,3 +5,4 @@ import { TestProjectSpec } from '../framework/frameworkTestSupport';
|
|||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
const REACT_PROJECT_ROOT = path.join(DATA_ROOT, 'react', 'dist');
|
||||
export const reactTestSpecification = new TestProjectSpec({ projectRoot: REACT_PROJECT_ROOT });
|
||||
export const reactWithLoopTestSpecification = new TestProjectSpec({ projectRoot: path.join(DATA_ROOT, 'react_with_loop', 'dist') });
|
||||
|
|
|
@ -2,118 +2,39 @@
|
|||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import * as testSetup from './testSetup';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { FrameworkTestContext, TestProjectSpec } from './framework/frameworkTestSupport';
|
||||
import { puppeteerSuite, puppeteerTest } from './puppeteer/puppeteerSuite';
|
||||
import { setBreakpoint } from './intTestSupport';
|
||||
import { THREAD_ID } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { URL } from 'url';
|
||||
import { activate } from '../../src/extension';
|
||||
import { BreakpointsWizard } from './wizards/breakpoints/breakpointsWizard';
|
||||
import { ExpectedFrame } from './wizards/breakpoints/implementation/stackTraceObjectAssertions';
|
||||
|
||||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
const SIMPLE_PROJECT_ROOT = path.join(DATA_ROOT, 'stackTrace');
|
||||
const TEST_SPEC = new TestProjectSpec( { projectRoot: SIMPLE_PROJECT_ROOT } );
|
||||
const TEST_URL = new URL(TEST_SPEC.props.url);
|
||||
|
||||
interface ExpectedSource {
|
||||
fileRelativePath?: string;
|
||||
urlRelativePath?: string;
|
||||
}
|
||||
|
||||
interface ExpectedFrame {
|
||||
name: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
source?: ExpectedSource;
|
||||
presentationHint?: string;
|
||||
}
|
||||
|
||||
function assertSourceMatches(actual: DebugProtocol.Source, expected: ExpectedSource, index: number) {
|
||||
if (expected == null) {
|
||||
assert.equal(actual, null, `Source was returned for frame ${index} but none was expected`);
|
||||
return;
|
||||
}
|
||||
|
||||
let expectedName: string;
|
||||
let expectedPath: string;
|
||||
|
||||
if (expected.fileRelativePath) {
|
||||
// Generate the expected path from the relative path and the project root
|
||||
expectedPath = path.join(TEST_SPEC.props.projectRoot, expected.fileRelativePath);
|
||||
expectedName = path.parse(expectedPath).base;
|
||||
} else if (expected.urlRelativePath) {
|
||||
// Generate the expected path from the relative path and the project url
|
||||
const url = new URL(TEST_URL.toString()); // Clone URL so we can update it
|
||||
url.pathname = expected.urlRelativePath;
|
||||
expectedName = url.host;
|
||||
expectedPath = url.toString();
|
||||
}
|
||||
|
||||
assert.equal(actual.name, expectedName, `Source name for frame ${index} does not match`);
|
||||
assert.equal(actual.path, expectedPath, `Source path for frame ${index} does not match`);
|
||||
}
|
||||
|
||||
function assertFrameMatches(actual: DebugProtocol.StackFrame, expected: ExpectedFrame, index: number) {
|
||||
assert.equal(actual.name, expected.name, `Name for frame ${index} does not match`);
|
||||
assert.equal(actual.line, expected.line, `Line number for frame ${index} does not match`);
|
||||
assert.equal(actual.column, expected.column, `Column number for frame ${index} does not match`);
|
||||
assert.equal(actual.presentationHint, expected.presentationHint, `Presentation hint for frame ${index} does not match`);
|
||||
assertSourceMatches(actual.source, expected.source, index);
|
||||
}
|
||||
|
||||
function assertResponseMatches(actual: DebugProtocol.StackTraceResponse, expectedFrames: ExpectedFrame[]) {
|
||||
// Check totalFrames property
|
||||
assert.equal(actual.body.totalFrames, expectedFrames.length, 'Property "totalFrames" does not match number of expected frames');
|
||||
|
||||
// Check array length
|
||||
const actualFrames = actual.body.stackFrames;
|
||||
assert.equal(actualFrames.length, expectedFrames.length, 'Number of stack frames in array does not match');
|
||||
|
||||
// Check each frame
|
||||
actualFrames.forEach((actualFrame, i) => {
|
||||
assertFrameMatches(actualFrame, expectedFrames[i], i);
|
||||
});
|
||||
}
|
||||
const TEST_SPEC = new TestProjectSpec( { projectRoot: SIMPLE_PROJECT_ROOT, projectSrc: SIMPLE_PROJECT_ROOT } );
|
||||
|
||||
interface StackTraceValidationConfig {
|
||||
suiteContext: FrameworkTestContext;
|
||||
page: puppeteer.Page;
|
||||
breakPointLabel: string;
|
||||
buttonIdToClick: string;
|
||||
args: DebugProtocol.StackTraceArguments;
|
||||
expectedFranes: ExpectedFrame[];
|
||||
format?: DebugProtocol.StackFrameFormat;
|
||||
expectedFrames: ExpectedFrame[];
|
||||
}
|
||||
|
||||
async function validateStackTrace(config: StackTraceValidationConfig): Promise<void> {
|
||||
// Find the breakpoint location for this test
|
||||
const location = config.suiteContext.breakpointLabels.get(config.breakPointLabel);
|
||||
|
||||
// Set the breakpoint, click the button, and wait for the breakpoint to hit
|
||||
const incBtn = await config.page.waitForSelector(config.buttonIdToClick);
|
||||
await setBreakpoint(config.suiteContext.debugClient, location);
|
||||
const clicked = incBtn.click();
|
||||
await config.suiteContext.debugClient.assertStoppedLocation('breakpoint', location);
|
||||
|
||||
// Get the stack trace
|
||||
const stackTraceResponse = await config.suiteContext.debugClient.send('stackTrace', config.args);
|
||||
const breakpoints = BreakpointsWizard.create(config.suiteContext.debugClient, TEST_SPEC);
|
||||
const breakpointWizard = breakpoints.at('app.js');
|
||||
|
||||
// Clean up the test before assertions, in case the assertions fail
|
||||
await config.suiteContext.debugClient.continueRequest();
|
||||
await clicked;
|
||||
const setStateBreakpoint = await breakpointWizard.breakpoint({
|
||||
text: "console.log('Test stack trace here')"
|
||||
});
|
||||
|
||||
// Assert the response is as expected
|
||||
try {
|
||||
assertResponseMatches(stackTraceResponse, config.expectedFranes);
|
||||
} catch (e) {
|
||||
const error: assert.AssertionError = e;
|
||||
error.message += '\nActual stack trace response: \n' + JSON.stringify(stackTraceResponse, null, 2);
|
||||
|
||||
throw error;
|
||||
}
|
||||
await setStateBreakpoint.assertIsHitThenResumeWhen(() => incBtn.click(), {stackTrace: config.expectedFrames, stackFrameFormat: config.format});
|
||||
}
|
||||
|
||||
puppeteerSuite('Stack Traces', TEST_SPEC, (suiteContext) => {
|
||||
|
@ -123,37 +44,37 @@ puppeteerSuite('Stack Traces', TEST_SPEC, (suiteContext) => {
|
|||
page: page,
|
||||
breakPointLabel: 'stackTraceBreakpoint',
|
||||
buttonIdToClick: '#button',
|
||||
args: {
|
||||
threadId: THREAD_ID
|
||||
},
|
||||
expectedFranes: [
|
||||
{ name: '(anonymous function)', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: 'inner', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
|
||||
format: {},
|
||||
expectedFrames: [
|
||||
{ name: '(anonymous function)', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: 'evalCallback', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: '(eval code)', line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
|
||||
{ name: 'timeoutCallback', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: '[ setTimeout ]', presentationHint: 'label'},
|
||||
{ name: 'buttonClick', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: 'onclick', line: 7, column: 49, source: { urlRelativePath: '/' }},
|
||||
{ name: 'buttonClick', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: 'onclick', line: 7, column: 49, source: { url: suiteContext.launchProject!.url }, presentationHint: 'normal'},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
puppeteerTest('Stack trace is generated with module formatting', suiteContext, async (_context, page) => {
|
||||
const url = suiteContext.launchProject!.url;
|
||||
await validateStackTrace({
|
||||
suiteContext: suiteContext,
|
||||
page: page,
|
||||
breakPointLabel: 'stackTraceBreakpoint',
|
||||
buttonIdToClick: '#button',
|
||||
args: {
|
||||
threadId: THREAD_ID,
|
||||
format: {
|
||||
module: true
|
||||
}
|
||||
format: {
|
||||
module: true
|
||||
},
|
||||
expectedFranes: [
|
||||
{ name: '(anonymous function) [app.js]', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: 'inner [app.js]', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
|
||||
expectedFrames: [
|
||||
{ name: '(anonymous function) [app.js]', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: 'evalCallback [app.js]', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: /\(eval code\) \[.*VM.*]/, line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
|
||||
{ name: 'timeoutCallback [app.js]', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: '[ setTimeout ]', presentationHint: 'label'},
|
||||
{ name: 'buttonClick [app.js]', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: `onclick [${TEST_URL.host}]`, line: 7, column: 49, source: { urlRelativePath: '/' }},
|
||||
{ name: 'buttonClick [app.js]', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: `onclick [${url.host}]`, line: 7, column: 49, source: { url }, presentationHint: 'normal'},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
@ -164,44 +85,43 @@ puppeteerSuite('Stack Traces', TEST_SPEC, (suiteContext) => {
|
|||
page: page,
|
||||
breakPointLabel: 'stackTraceBreakpoint',
|
||||
buttonIdToClick: '#button',
|
||||
args: {
|
||||
threadId: THREAD_ID,
|
||||
format: {
|
||||
line: true,
|
||||
}
|
||||
format: {
|
||||
line: true,
|
||||
},
|
||||
expectedFranes: [
|
||||
{ name: '(anonymous function) Line 7', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: 'inner Line 8', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
|
||||
expectedFrames: [
|
||||
{ name: '(anonymous function) Line 11', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: 'evalCallback Line 12', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: '(eval code) Line 1', line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
|
||||
{ name: 'timeoutCallback Line 6', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: '[ setTimeout ]', presentationHint: 'label'},
|
||||
{ name: 'buttonClick Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: 'onclick Line 7', line: 7, column: 49, source: { urlRelativePath: '/' }},
|
||||
{ name: 'buttonClick Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: 'onclick Line 7', line: 7, column: 49, source: { url: suiteContext.launchProject!.url }, presentationHint: 'normal'},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
puppeteerTest('Stack trace is generated with all formatting', suiteContext, async (_context, page) => {
|
||||
const url = suiteContext.launchProject!.url;
|
||||
await validateStackTrace({
|
||||
suiteContext: suiteContext,
|
||||
page: page,
|
||||
breakPointLabel: 'stackTraceBreakpoint',
|
||||
buttonIdToClick: '#button',
|
||||
args: {
|
||||
threadId: THREAD_ID,
|
||||
format: {
|
||||
parameters: true,
|
||||
parameterTypes: true,
|
||||
parameterNames: true,
|
||||
line: true,
|
||||
module: true
|
||||
}
|
||||
format: {
|
||||
parameters: true,
|
||||
parameterTypes: true,
|
||||
parameterNames: true,
|
||||
line: true,
|
||||
module: true
|
||||
},
|
||||
expectedFranes: [
|
||||
{ name: '(anonymous function) [app.js] Line 7', line: 7, column: 9, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: 'inner [app.js] Line 8', line: 8, column: 7, source: { fileRelativePath: 'app.js' }},
|
||||
expectedFrames: [
|
||||
{ name: '(anonymous function) [app.js] Line 11', line: 11, column: 9, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: 'evalCallback [app.js] Line 12', line: 12, column: 7, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: /\(eval code\) \[.*VM.*] Line 1/, line: 1, column: 1, source: { evalCode: true }, presentationHint: 'normal'},
|
||||
{ name: 'timeoutCallback [app.js] Line 6', line: 6, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: '[ setTimeout ]', presentationHint: 'label'},
|
||||
{ name: 'buttonClick [app.js] Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }},
|
||||
{ name: `onclick [${TEST_URL.host}] Line 7`, line: 7, column: 49, source: { urlRelativePath: '/' }},
|
||||
{ name: 'buttonClick [app.js] Line 2', line: 2, column: 5, source: { fileRelativePath: 'app.js' }, presentationHint: 'normal'},
|
||||
{ name: `onclick [${url.host}] Line 7`, line: 7, column: 49, source: { url }, presentationHint: 'normal'},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,26 +9,28 @@ import { createServer } from 'http-server';
|
|||
import * as ts from 'vscode-chrome-debug-core-testsupport';
|
||||
|
||||
import * as testSetup from './testSetup';
|
||||
import { HttpOrHttpsServer } from './types/server';
|
||||
|
||||
suite('Stepping', () => {
|
||||
const DATA_ROOT = testSetup.DATA_ROOT;
|
||||
|
||||
let dc: ts.ExtendedDebugClient;
|
||||
setup(() => {
|
||||
return testSetup.setup()
|
||||
setup(function () {
|
||||
return testSetup.setup(this)
|
||||
.then(_dc => dc = _dc);
|
||||
});
|
||||
|
||||
let server: any;
|
||||
teardown(() => {
|
||||
let server: HttpOrHttpsServer | null;
|
||||
teardown(async () => {
|
||||
if (server) {
|
||||
server.close();
|
||||
server.close(err => console.log('Error closing server in teardown: ' + (err && err.message)));
|
||||
server = null;
|
||||
}
|
||||
|
||||
return testSetup.teardown();
|
||||
await testSetup.teardown();
|
||||
});
|
||||
|
||||
suite('skipFiles', () => {
|
||||
suite.skip('skipFiles', () => {
|
||||
test('when generated script is skipped via regex, the source can be un-skipped', async () => {
|
||||
const testProjectRoot = path.join(DATA_ROOT, 'calls-between-merged-files');
|
||||
const sourceA = path.join(testProjectRoot, 'sourceA.ts');
|
||||
|
|
|
@ -5,21 +5,39 @@
|
|||
|
||||
import * as path from 'path';
|
||||
import * as tmp from 'tmp';
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import * as _ from 'lodash';
|
||||
import * as ts from 'vscode-chrome-debug-core-testsupport';
|
||||
|
||||
import { ILaunchRequestArgs } from '../../src/chromeDebugInterfaces';
|
||||
import { Dictionary } from 'lodash';
|
||||
import { logCallsTo, getDebugAdapterLogFilePath, setTestLogName } from './utils/logging';
|
||||
import { IBeforeAndAfterContext, ITestCallbackContext } from 'mocha';
|
||||
import { killAllChrome } from '../testUtils';
|
||||
import { DefaultTimeoutMultiplier } from './utils/waitUntilReadyWithTimeout';
|
||||
|
||||
const DEBUG_ADAPTER = './out/src/chromeDebug.js';
|
||||
|
||||
let testLaunchProps: ILaunchRequestArgs;
|
||||
let testLaunchProps: ILaunchRequestArgs & Dictionary<unknown> | undefined;
|
||||
|
||||
export const isThisV2 = false;
|
||||
export const isThisV2 = true;
|
||||
export const isThisV1 = !isThisV2;
|
||||
export const isWindows = process.platform === 'win32';
|
||||
|
||||
function formLaunchArgs(launchArgs: ILaunchRequestArgs): void {
|
||||
function formLaunchArgs(launchArgs: ILaunchRequestArgs & Dictionary<unknown>, testTitle: string): void {
|
||||
launchArgs.trace = 'verbose';
|
||||
launchArgs.logTimestamps = true;
|
||||
launchArgs.disableNetworkCache = true;
|
||||
launchArgs.logFilePath = getDebugAdapterLogFilePath(testTitle);
|
||||
|
||||
if (!launchArgs.runtimeExecutable) {
|
||||
launchArgs.runtimeExecutable = puppeteer.executablePath();
|
||||
}
|
||||
|
||||
const hideWindows = process.env['TEST_DA_HIDE_WINDOWS'] === 'true';
|
||||
if (hideWindows) {
|
||||
launchArgs.runtimeArgs = ['--headless', '--disable-gpu'];
|
||||
}
|
||||
|
||||
// Start with a clean userDataDir for each test run
|
||||
const tmpDir = tmp.dirSync({ prefix: 'chrome2-' });
|
||||
|
@ -32,21 +50,55 @@ function formLaunchArgs(launchArgs: ILaunchRequestArgs): void {
|
|||
}
|
||||
}
|
||||
|
||||
function patchLaunchArgs(launchArgs: ILaunchRequestArgs): void {
|
||||
formLaunchArgs(launchArgs);
|
||||
function patchLaunchArgs(launchArgs: ILaunchRequestArgs, testTitle: string): void {
|
||||
formLaunchArgs(launchArgs, testTitle);
|
||||
}
|
||||
|
||||
export const lowercaseDriveLetterDirname = __dirname.charAt(0).toLowerCase() + __dirname.substr(1);
|
||||
export const PROJECT_ROOT = path.join(lowercaseDriveLetterDirname, '../../../');
|
||||
export const DATA_ROOT = path.join(PROJECT_ROOT, 'testdata/');
|
||||
|
||||
export function setup(port?: number, launchProps?: ILaunchRequestArgs) {
|
||||
/** Default setup for all our tests, using the context of the setup method
|
||||
* - Best practise: The new best practise is to use the DefaultFixture when possible instead of calling this method directly
|
||||
*/
|
||||
export async function setup(context: IBeforeAndAfterContext | ITestCallbackContext, port?: number, launchProps?: ILaunchRequestArgs): Promise<ts.ExtendedDebugClient> {
|
||||
const currentTest = _.defaultTo(context.currentTest, context.test);
|
||||
return setupWithTitle(currentTest.fullTitle(), port, launchProps);
|
||||
}
|
||||
|
||||
/** Default setup for all our tests, using the test title
|
||||
* - Best practise: The new best practise is to use the DefaultFixture when possible instead of calling this method directly
|
||||
*/
|
||||
export async function setupWithTitle(testTitle: string, port?: number, launchProps?: ILaunchRequestArgs): Promise<ts.ExtendedDebugClient> {
|
||||
// killAllChromesOnWin32(); // Kill chrome.exe instances before the tests. Killing them after the tests is not as reliable. If setup fails, teardown is not executed.
|
||||
setTestLogName(testTitle);
|
||||
|
||||
if (!port) {
|
||||
const daPort = process.env['TEST_DA_PORT'];
|
||||
port = daPort
|
||||
? parseInt(daPort, 10)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (launchProps) {
|
||||
testLaunchProps = launchProps;
|
||||
}
|
||||
return ts.setup({entryPoint: DEBUG_ADAPTER, type: 'chrome', patchLaunchArgs: patchLaunchArgs, port: port});
|
||||
|
||||
const debugClient = await ts.setup({ entryPoint: DEBUG_ADAPTER, type: 'chrome', patchLaunchArgs: args => patchLaunchArgs(args, testTitle), port: port });
|
||||
debugClient.defaultTimeout = DefaultTimeoutMultiplier * 10000 /*10 seconds*/;
|
||||
|
||||
const wrappedDebugClient = logCallsTo(debugClient, 'DebugAdapterClient');
|
||||
return wrappedDebugClient;
|
||||
}
|
||||
|
||||
export function teardown() {
|
||||
return ts.teardown();
|
||||
export async function teardown() {
|
||||
await ts.teardown();
|
||||
}
|
||||
|
||||
export function killAllChromesOnWin32() {
|
||||
if (process.platform === 'win32') {
|
||||
// We only need to kill the chrome.exe instances on the Windows agent
|
||||
// TODO: Figure out a way to remove this
|
||||
killAllChrome();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
// Improve the default typing of Object.keys(o: T) to be keyof T (without the symbols)
|
||||
interface ObjectConstructor {
|
||||
keys<T>(o: T): WithoutSymbols<(keyof T)>[];
|
||||
}
|
||||
declare var Object: ObjectConstructor;
|
||||
|
||||
/** Return the strings that form S and ignore the symbols */
|
||||
type WithoutSymbols<S> = S extends string ? S : string;
|
|
@ -0,0 +1,4 @@
|
|||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
|
||||
export type HttpOrHttpsServer = http.Server | https.Server;
|
|
@ -1,5 +1,5 @@
|
|||
import * as _ from 'lodash';
|
||||
import { createColumnNumber, createLineNumber } from '../core-v2/chrome/internal/locations/subtypes';
|
||||
import { createColumnNumber, createLineNumber, LineNumber } from '../core-v2/chrome/internal/locations/subtypes';
|
||||
import { utils } from 'vscode-chrome-debug-core';
|
||||
import { Position } from '../core-v2/chrome/internal/locations/location';
|
||||
|
||||
|
@ -9,8 +9,7 @@ export async function findPositionOfTextInFile(filePath: string, text: string):
|
|||
const textStartIndex = contents.indexOf(text);
|
||||
|
||||
if (textStartIndex >= 0) {
|
||||
const contentsBeforeText = contents.substr(0, textStartIndex);
|
||||
const textLineNumber = createLineNumber(_.countBy(contentsBeforeText, c => c === '\n')['true'] || 0);
|
||||
const textLineNumber = findLineNumber(contents, textStartIndex);
|
||||
const lastNewLineBeforeTextIndex = contents.lastIndexOf('\n', textStartIndex);
|
||||
const textColumNumber = createColumnNumber(textStartIndex - (lastNewLineBeforeTextIndex + 1));
|
||||
return new Position(textLineNumber, textColumNumber);
|
||||
|
@ -18,3 +17,10 @@ export async function findPositionOfTextInFile(filePath: string, text: string):
|
|||
throw new Error(`Couldn't find ${text} in ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function findLineNumber(contents: string, characterIndex: number): LineNumber {
|
||||
const contentsBeforeCharacter = contents.substr(0, characterIndex);
|
||||
const textLineNumber = createLineNumber(_.countBy(contentsBeforeCharacter, c => c === '\n')['true'] || 0);
|
||||
return textLineNumber;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
import { utils } from 'vscode-chrome-debug-core';
|
||||
|
||||
/**
|
||||
* The test normally run very fast, so it's difficult to see what actions they are taking in the browser.
|
||||
* We can use the HumanSlownessSimulator to artifically slow some classes like the puppeteer classes, so it's the actions
|
||||
* will be taken at a lower speed, and it'll be easier to see and understand what is happening
|
||||
*/
|
||||
export class HumanSlownessSimulator {
|
||||
public constructor(private readonly _slownessInMillisecondsValueGenerator: () => number = () => 500) { }
|
||||
|
||||
public simulateSlowness(): Promise<void> {
|
||||
return utils.promiseTimeout(undefined, this._slownessInMillisecondsValueGenerator());
|
||||
}
|
||||
|
||||
public wrap<T extends object>(object: T): T {
|
||||
return new HumanSpeedProxy(this, object).wrapped();
|
||||
}
|
||||
}
|
||||
|
||||
export class HumanSpeedProxy<T extends object> {
|
||||
constructor(
|
||||
private readonly _humanSlownessSimulator: HumanSlownessSimulator,
|
||||
private readonly _objectToWrap: T) {
|
||||
}
|
||||
|
||||
public wrapped(): T {
|
||||
const handler = {
|
||||
get: <K extends keyof T>(target: T, propertyKey: K, _receiver: any) => {
|
||||
this._humanSlownessSimulator.simulateSlowness();
|
||||
const originalPropertyValue = target[propertyKey];
|
||||
if (typeof originalPropertyValue === 'function') {
|
||||
return (...args: any) => {
|
||||
const result = originalPropertyValue.apply(target, args);
|
||||
if (result && result.then) {
|
||||
// Currently we only slow down async operations
|
||||
return result.then(async (promiseResult: object) => {
|
||||
await this._humanSlownessSimulator.simulateSlowness();
|
||||
return typeof promiseResult === 'object'
|
||||
? this._humanSlownessSimulator.wrap(promiseResult)
|
||||
: promiseResult;
|
||||
}, (rejection: unknown) => {
|
||||
return rejection;
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return originalPropertyValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Proxy<T>(this._objectToWrap, handler);
|
||||
}
|
||||
}
|
||||
|
||||
const humanSlownessSimulator = new HumanSlownessSimulator();
|
||||
|
||||
const humanSlownessEnabeld = process.env.RUN_TESTS_SLOWLY === 'true';
|
||||
|
||||
export function slowToHumanLevel<T extends object>(object: T): T {
|
||||
return humanSlownessEnabeld
|
||||
? humanSlownessSimulator.wrap(object)
|
||||
: object;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import * as path from 'path';
|
||||
import { logger, } from 'vscode-debugadapter';
|
||||
import { LogLevel } from 'vscode-debugadapter/lib/logger';
|
||||
import { MethodsCalledLogger, IMethodsCalledLoggerConfiguration, MethodsCalledLoggerConfiguration, ReplacementInstruction, wrapWithMethodLogger } from '../core-v2/chrome/logging/methodsCalledLogger';
|
||||
|
||||
const useDateTimeInLog = false;
|
||||
function dateTimeForFilePath(): string {
|
||||
return new Date().toISOString().replace(/:/g, '').replace('T', ' ').replace(/\.[0-9]+^/, '');
|
||||
}
|
||||
|
||||
function dateTimeForFilePathIfNeeded() {
|
||||
return useDateTimeInLog ? `-${dateTimeForFilePath()}` : '';
|
||||
}
|
||||
|
||||
const logsFolderPath = path.resolve(process.cwd(), 'logs');
|
||||
|
||||
export function getDebugAdapterLogFilePath(testTitle: string): string {
|
||||
return logFilePath(testTitle, 'DA');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a title to an equivalent title that can be used as a filename (We use this to convert the name of our tests into the name of the logfile for that test)
|
||||
*/
|
||||
function sanitizeTestTitle(testTitle: string) {
|
||||
return testTitle
|
||||
.replace(/[:\/\\]/g, '-')
|
||||
|
||||
// These replacements are needed for the hit count breakpoint tests, which have these characters in their title
|
||||
.replace(/ > /g, ' bigger than ')
|
||||
.replace(/ < /g, ' smaller than ')
|
||||
.replace(/ >= /g, ' bigger than or equal to ')
|
||||
.replace(/ <= /g, ' smaller than or equal to ');
|
||||
}
|
||||
|
||||
function logFilePath(testTitle: string, logType: string) {
|
||||
return path.join(logsFolderPath, `${process.platform}-${sanitizeTestTitle(testTitle)}-${logType}${dateTimeForFilePathIfNeeded()}.log`);
|
||||
}
|
||||
|
||||
logger.init(() => { });
|
||||
|
||||
// Dispose the logger on unhandled errors, so it'll flush the remaining contents of the log...
|
||||
process.on('uncaughtException', () => logger.dispose());
|
||||
process.on('unhandledRejection', () => logger.dispose());
|
||||
|
||||
let currentTestTitle = '';
|
||||
export function setTestLogName(testTitle: string): void {
|
||||
// We call setTestLogName in the common setup code. We want to call it earlier in puppeteer tests to get the logs even when the setup fails
|
||||
// So we write this code to be able to call it two times, and the second time will get ignored
|
||||
if (testTitle !== currentTestTitle) {
|
||||
logger.setup(LogLevel.Verbose, logFilePath(testTitle, 'TEST'));
|
||||
testTitle = currentTestTitle;
|
||||
}
|
||||
}
|
||||
|
||||
class PuppeteerMethodsCalledLoggerConfiguration implements IMethodsCalledLoggerConfiguration {
|
||||
private readonly _wrapped = new MethodsCalledLoggerConfiguration('', []);
|
||||
public readonly replacements: ReplacementInstruction[] = [];
|
||||
|
||||
public customizeResult<T>(methodName: string | symbol | number, args: any, result: T): T {
|
||||
if (methodName === 'waitForSelector' && typeof result === 'object' && args.length >= 1) {
|
||||
return wrapWithMethodLogger(<T & object>result, args[0]);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public customizeArgumentsBeforeCall(receiverName: string, methodName: string | symbol | number, args: object[]): void {
|
||||
this._wrapped.customizeArgumentsBeforeCall(receiverName, methodName, args);
|
||||
}
|
||||
}
|
||||
|
||||
export function logCallsTo<T extends object>(object: T, name: string): T {
|
||||
return new MethodsCalledLogger(new PuppeteerMethodsCalledLoggerConfiguration(), object, name).wrapped();
|
||||
}
|
|
@ -2,4 +2,4 @@ export async function asyncRepeatSerially(howManyTimes: number, action: () => Pr
|
|||
for (let index = 0; index < howManyTimes; ++index) {
|
||||
await action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import * as _ from 'lodash';
|
||||
import { utils } from 'vscode-chrome-debug-core';
|
||||
|
||||
export async function waitUntilReadyWithTimeout(isReady: () => boolean, maxWaitTimeInMilliseconds = 5000) {
|
||||
// The VSTS agents run slower than our machines. Use this value to reduce proportinoally the timeouts in your dev machine
|
||||
export const DefaultTimeoutMultiplier = parseFloat(_.defaultTo(process.env['TEST_TIMEOUT_MULTIPLIER'], '1'));
|
||||
|
||||
/**
|
||||
* Wait until the isReady condition evaluates to true. This method will evaluate it every 50 milliseconds until it returns true. It will time-out after maxWaitTimeInMilliseconds milliseconds
|
||||
*/
|
||||
export async function waitUntilReadyWithTimeout(isReady: () => boolean, maxWaitTimeInMilliseconds = DefaultTimeoutMultiplier * 30000 /* 30 seconds */) {
|
||||
const maximumDateTimeToWaitUntil = Date.now() + maxWaitTimeInMilliseconds;
|
||||
|
||||
while (!isReady() && Date.now() < maximumDateTimeToWaitUntil) {
|
||||
await utils.promiseTimeout(null, 50);
|
||||
await utils.promiseTimeout(undefined, 10 /*ms*/);
|
||||
}
|
||||
|
||||
if (!isReady()) {
|
||||
throw new Error(`Timed-out after waiting for condition to be ready for ${maxWaitTimeInMilliseconds}ms`);
|
||||
throw new Error(`Timed-out after waiting for condition to be ready for ${maxWaitTimeInMilliseconds}ms. Condition: ${isReady}`);
|
||||
}
|
||||
}
|
|
@ -3,13 +3,14 @@ import { IBPActionWhenHit } from '../../core-v2/chrome/internal/breakpoints/bpAc
|
|||
import { InternalFileBreakpointsWizard } from './implementation/internalFileBreakpointsWizard';
|
||||
import { RemoveProperty } from '../../core-v2/typeUtils';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { IVerifications } from './implementation/breakpointsAssertions';
|
||||
|
||||
export class BreakpointWizard {
|
||||
private _state: IBreakpointSetOrUnsetState = new BreakpointUnsetState(this, this._internal, this.changeStateFunction());
|
||||
|
||||
public constructor(
|
||||
private readonly _internal: InternalFileBreakpointsWizard, public readonly position: Position,
|
||||
public readonly actionWhenHit: IBPActionWhenHit, public readonly name: string) { }
|
||||
public readonly actionWhenHit: IBPActionWhenHit, public readonly name: string, public readonly boundPosition: Position) { }
|
||||
|
||||
public async setThenWaitForVerifiedThenValidate(): Promise<BreakpointWizard> {
|
||||
await this.setWithoutVerifying();
|
||||
|
@ -33,8 +34,13 @@ export class BreakpointWizard {
|
|||
return this;
|
||||
}
|
||||
|
||||
public async assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<BreakpointWizard> {
|
||||
await this._state.assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit, expectedStackTrace);
|
||||
public async assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications = {}): Promise<BreakpointWizard> {
|
||||
await this._state.assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit, verifications);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async assertIsHitThenResume(verifications: IVerifications) {
|
||||
await this._state.assertIsHitThenResume(verifications);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -59,7 +65,8 @@ export type ChangeBreakpointWizardState = (newState: IBreakpointSetOrUnsetState)
|
|||
export interface IBreakpointSetOrUnsetState {
|
||||
set(): Promise<void>;
|
||||
unset(): Promise<void>;
|
||||
assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void>;
|
||||
assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void>;
|
||||
assertIsHitThenResume(verifications: IVerifications): Promise<void>;
|
||||
assertIsVerified(): void;
|
||||
waitUntilVerified(): Promise<void>;
|
||||
}
|
||||
|
@ -80,8 +87,23 @@ class BreakpointSetState implements IBreakpointSetOrUnsetState {
|
|||
this._changeState(new BreakpointUnsetState(this._breakpoint, this._internal, this._changeState));
|
||||
}
|
||||
|
||||
public assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
|
||||
return this._internal.assertIsHitThenResumeWhen(this._breakpoint, lastActionToMakeBreakpointHit, expectedStackTrace);
|
||||
/**
|
||||
* This method is intended to avoid hangs when performing a puppeteer action that will get blocked while the debuggee hits a breakpoint.
|
||||
*
|
||||
* The method will execute the puppeteer action, verify that the breakpoint is hit, and afterwards verify that the puppeteer action was properly finished.
|
||||
*
|
||||
* More details:
|
||||
* The method will also verify that the pause was in the exact locatio that the breakpoint is located, and any other verifications specified in the verifications parameter
|
||||
*/
|
||||
public assertIsHitThenResumeWhen(lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
|
||||
return this._internal.assertIsHitThenResumeWhen(this._breakpoint, lastActionToMakeBreakpointHit, verifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the debuggee is paused due to this breakpoint, and perform a customizable list of extra verifications
|
||||
*/
|
||||
public assertIsHitThenResume(verifications: IVerifications): Promise<void> {
|
||||
return this._internal.assertIsHitThenResume(this._breakpoint, verifications);
|
||||
}
|
||||
|
||||
public assertIsVerified(): void {
|
||||
|
@ -109,7 +131,11 @@ export class BreakpointUnsetState implements IBreakpointSetOrUnsetState {
|
|||
throw new Error(`Can't unset a breakpoint that is already unset`);
|
||||
}
|
||||
|
||||
public assertIsHitThenResumeWhen(_lastActionToMakeBreakpointHit: () => Promise<void>, _expectedStackTrace: string): Promise<void> {
|
||||
public assertIsHitThenResumeWhen(_lastActionToMakeBreakpointHit: () => Promise<void>, _verifications: IVerifications): Promise<void> {
|
||||
throw new Error(`Can't expect to hit a breakpoint that is unset`);
|
||||
}
|
||||
|
||||
public assertIsHitThenResume(_verifications: IVerifications): Promise<void> {
|
||||
throw new Error(`Can't expect to hit a breakpoint that is unset`);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { TestProjectSpec } from '../../framework/frameworkTestSupport';
|
||||
import { InternalFileBreakpointsWizard } from './implementation/internalFileBreakpointsWizard';
|
||||
import { InternalFileBreakpointsWizard, BreakpointStatusChangedWithId } from './implementation/internalFileBreakpointsWizard';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { ValidatedMap } from '../../core-v2/chrome/collections/validatedMap';
|
||||
import { wrapWithMethodLogger } from '../../core-v2/chrome/logging/methodsCalledLogger';
|
||||
import assert = require('assert');
|
||||
import { FileBreakpointsWizard } from './fileBreakpointsWizard';
|
||||
import { waitUntilReadyWithTimeout } from '../../utils/waitUntilReadyWithTimeout';
|
||||
import { isThisV2, isThisV1 } from '../../testSetup';
|
||||
import { BreakpointWizard } from './breakpointWizard';
|
||||
import { expect } from 'chai';
|
||||
import { PausedWizard } from '../pausedWizard';
|
||||
|
||||
export class BreakpointsWizard {
|
||||
private _state: IEventForConsumptionAvailabilityState = new NoEventAvailableToBeConsumed(this.changeStateFunction());
|
||||
private readonly _pausedWizard = PausedWizard.forClient(this._client);
|
||||
private readonly _pathToFileWizard = new ValidatedMap<string, InternalFileBreakpointsWizard>();
|
||||
|
||||
private constructor(private readonly _client: ExtendedDebugClient, private readonly _project: TestProjectSpec) {
|
||||
this._client.on('stopped', stopped => this._state.onPaused(stopped));
|
||||
this._client.on('continued', continued => this._state.onResumed(continued));
|
||||
this._client.on('breakpoint', breakpointStatusChange => this.onBreakpointStatusChange(breakpointStatusChange));
|
||||
this._client.on('breakpoint', breakpointStatusChange => this.onBreakpointStatusChange(breakpointStatusChange.body));
|
||||
}
|
||||
|
||||
public get project() {
|
||||
return this._project;
|
||||
}
|
||||
|
||||
public static create(debugClient: ExtendedDebugClient, testProjectSpecification: TestProjectSpec): BreakpointsWizard {
|
||||
|
@ -26,118 +27,46 @@ export class BreakpointsWizard {
|
|||
|
||||
public at(filePath: string): FileBreakpointsWizard {
|
||||
return wrapWithMethodLogger(new FileBreakpointsWizard(this._pathToFileWizard.getOrAdd(filePath,
|
||||
() => new InternalFileBreakpointsWizard(this, this._client, this._project.src(filePath)))));
|
||||
() => new InternalFileBreakpointsWizard(wrapWithMethodLogger(this), this._client, this._project.src(filePath)))));
|
||||
}
|
||||
|
||||
public async assertNotPaused(): Promise<void> {
|
||||
await this._state.assertNotPaused();
|
||||
public async waitAndConsumePausedEvent(_breakpoint: BreakpointWizard): Promise<void> {
|
||||
// TODO: Should we validate the stack trace is on breakpoint here?
|
||||
await this._pausedWizard.waitAndConsumePausedEvent(pausedInfo => {
|
||||
expect(pausedInfo.reason).to.equal('breakpoint');
|
||||
});
|
||||
}
|
||||
|
||||
public assertIsPaused(): void {
|
||||
this._state.assertIsPaused();
|
||||
/**
|
||||
* Instruct the debuggee to resume, and verify that the Debug-Adapter sends the proper notification after that happens
|
||||
*/
|
||||
public async resume(): Promise<void> {
|
||||
return this._pausedWizard.resume();
|
||||
}
|
||||
|
||||
public async waitUntilJustResumed(): Promise<void> {
|
||||
await waitUntilReadyWithTimeout(() => this._state instanceof EventAvailableToBeConsumed);
|
||||
public async waitAndConsumeResumedEvent(): Promise<void> {
|
||||
return this._pausedWizard.waitAndConsumeResumedEvent();
|
||||
}
|
||||
|
||||
await this._state.waitUntilJustResumed();
|
||||
public async waitAndAssertNoMoreEvents(): Promise<void> {
|
||||
return this._pausedWizard.waitAndAssertNoMoreEvents();
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return 'Breakpoints';
|
||||
}
|
||||
|
||||
private onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
|
||||
for (const fileWizard of this._pathToFileWizard.values()) {
|
||||
fileWizard.onBreakpointStatusChange(breakpointStatusChanged);
|
||||
private onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent['body']): void {
|
||||
if (this.isBreakpointStatusChangedWithId(breakpointStatusChanged)) {
|
||||
|
||||
// TODO: Update this code to only send the breakpoint to the file that owns it
|
||||
for (const fileWizard of this._pathToFileWizard.values()) {
|
||||
fileWizard.onBreakpointStatusChange(breakpointStatusChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private changeStateFunction(): (newState: IEventForConsumptionAvailabilityState) => void {
|
||||
return newState => this._state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
interface IEventForConsumptionAvailabilityState {
|
||||
readonly latestEvent: DebugProtocol.StoppedEvent | DebugProtocol.ContinuedEvent;
|
||||
|
||||
onPaused(stopped: DebugProtocol.StoppedEvent): void;
|
||||
onResumed(continued: DebugProtocol.ContinuedEvent): void;
|
||||
|
||||
waitUntilJustResumed(): Promise<void>;
|
||||
assertIsPaused(): void;
|
||||
assertNotPaused(): Promise<void>;
|
||||
}
|
||||
|
||||
type ChangeState = (newState: IEventForConsumptionAvailabilityState) => void;
|
||||
|
||||
class EventAvailableToBeConsumed implements IEventForConsumptionAvailabilityState {
|
||||
public constructor(private readonly _changeState: ChangeState, public readonly latestEvent: DebugProtocol.StoppedEvent | DebugProtocol.ContinuedEvent) { }
|
||||
|
||||
public onPaused(stopped: DebugProtocol.StoppedEvent): void {
|
||||
if (isThisV1 && this.latestEvent.event === 'continued') {
|
||||
this._changeState(new EventAvailableToBeConsumed(this._changeState, stopped));
|
||||
} else {
|
||||
throw new Error(`Expected to consume previous event: ${JSON.stringify(this.latestEvent)} before receiving a new stopped event: ${JSON.stringify(stopped)}`);
|
||||
}
|
||||
}
|
||||
|
||||
public onResumed(continued: DebugProtocol.ContinuedEvent): void {
|
||||
if (isThisV2) {
|
||||
throw new Error(`Expected to consume previous event: ${JSON.stringify(this.latestEvent)} before receiving a new continued event: ${JSON.stringify(continued)}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async waitUntilJustResumed(): Promise<void> {
|
||||
if (this.latestEvent.event === 'continued') {
|
||||
this._changeState(new NoEventAvailableToBeConsumed(this._changeState));
|
||||
}
|
||||
}
|
||||
|
||||
public assertIsPaused(): void {
|
||||
if (this.latestEvent.event === 'stopped') {
|
||||
this._changeState(new NoEventAvailableToBeConsumed(this._changeState));
|
||||
}
|
||||
}
|
||||
|
||||
public async assertNotPaused(): Promise<void> {
|
||||
expect(this.latestEvent.event, `Expected that there was not new paused event to be consumed, and that the debugger wasn't paused yet the state was: ${this}`)
|
||||
.to.not.equal('stopped');
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `Event available to be consumed: ${JSON.stringify(this.latestEvent)}`;
|
||||
}
|
||||
}
|
||||
|
||||
class NoEventAvailableToBeConsumed implements IEventForConsumptionAvailabilityState {
|
||||
public constructor(private readonly _changeState: ChangeState) { }
|
||||
|
||||
public get latestEvent(): never {
|
||||
throw new Error(`There is no event available to be consumed`);
|
||||
}
|
||||
|
||||
public onPaused(stopped: DebugProtocol.StoppedEvent): void {
|
||||
this._changeState(new EventAvailableToBeConsumed(this._changeState, stopped));
|
||||
}
|
||||
|
||||
public onResumed(continued: DebugProtocol.ContinuedEvent): void {
|
||||
this._changeState(new EventAvailableToBeConsumed(this._changeState, continued));
|
||||
}
|
||||
|
||||
public waitUntilJustResumed(): Promise<void> {
|
||||
throw new Error(`There is no event available to be consumed`);
|
||||
}
|
||||
|
||||
public assertIsPaused(): void {
|
||||
throw new Error(`There is no event available to be consumed`);
|
||||
}
|
||||
|
||||
public async assertNotPaused(): Promise<void> {
|
||||
// Always true for this state
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `NoEventAvailableToBeConsumed`;
|
||||
private isBreakpointStatusChangedWithId(statusChanged: DebugProtocol.BreakpointEvent['body']): statusChanged is BreakpointStatusChangedWithId {
|
||||
return statusChanged.breakpoint.id !== undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ import { BreakpointWizard } from './breakpointWizard';
|
|||
import { InternalFileBreakpointsWizard } from './implementation/internalFileBreakpointsWizard';
|
||||
import { PromiseOrNot } from 'vscode-chrome-debug-core';
|
||||
import { wrapWithMethodLogger } from '../../core-v2/chrome/logging/methodsCalledLogger';
|
||||
import { PauseOnHitCount } from '../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
|
||||
|
||||
export interface IBreakpointOptions {
|
||||
lineText: string;
|
||||
text: string;
|
||||
boundText?: string;
|
||||
}
|
||||
|
||||
export interface IHitCountBreakpointOptions extends IBreakpointOptions {
|
||||
|
@ -14,15 +16,26 @@ export interface IHitCountBreakpointOptions extends IBreakpointOptions {
|
|||
export class FileBreakpointsWizard {
|
||||
public constructor(private readonly _internal: InternalFileBreakpointsWizard) { }
|
||||
|
||||
public async breakpoint(options: IBreakpointOptions): Promise<BreakpointWizard> {
|
||||
const wrappedBreakpoint = wrapWithMethodLogger(await this._internal.breakpoint({
|
||||
text: options.text,
|
||||
boundText: options.boundText,
|
||||
name: `BP @ ${options.text}`
|
||||
}));
|
||||
|
||||
return wrappedBreakpoint.setThenWaitForVerifiedThenValidate();
|
||||
}
|
||||
|
||||
public async hitCountBreakpoint(options: IHitCountBreakpointOptions): Promise<BreakpointWizard> {
|
||||
return (await (await this.unsetHitCountBreakpoint(options)).setThenWaitForVerifiedThenValidate());
|
||||
}
|
||||
|
||||
public async unsetHitCountBreakpoint(options: IHitCountBreakpointOptions): Promise<BreakpointWizard> {
|
||||
return wrapWithMethodLogger(await this._internal.hitCountBreakpoint({
|
||||
lineText: options.lineText,
|
||||
hitCountCondition: options.hitCountCondition,
|
||||
name: `BP @ ${options.lineText}`
|
||||
return wrapWithMethodLogger(await this._internal.breakpoint({
|
||||
text: options.text,
|
||||
boundText: options.boundText,
|
||||
actionWhenHit: new PauseOnHitCount(options.hitCountCondition),
|
||||
name: `BP @ ${options.text}`
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BreakpointWizard } from '../breakpointWizard';
|
||||
import { ValidatedMap } from '../../../core-v2/chrome/collections/validatedMap';
|
||||
import { IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate } from './internalFileBreakpointsWizard';
|
||||
import { BreakpointsAssertions } from './breakpointsAssertions';
|
||||
import { IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate, BreakpointStatusChangedWithId } from './internalFileBreakpointsWizard';
|
||||
import { BreakpointsAssertions, IVerifications } from './breakpointsAssertions';
|
||||
import { BreakpointsWizard } from '../breakpointsWizard';
|
||||
|
||||
export class PerformChangesImmediatelyState implements IBreakpointsBatchingStrategy {
|
||||
|
@ -35,9 +39,9 @@ export class PerformChangesImmediatelyState implements IBreakpointsBatchingStrat
|
|||
await this._internal.sendBreakpointsToClient(new BreakpointsUpdate([], [breakpointWizard], remainingBreakpoints));
|
||||
}
|
||||
|
||||
public onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
|
||||
const breakpoint = this._idToBreakpoint.get(breakpointStatusChanged.body.breakpoint.id);
|
||||
this.currentBreakpointsMapping.setAndReplaceIfExist(breakpoint, breakpointStatusChanged.body.breakpoint);
|
||||
public onBreakpointStatusChange(breakpointStatusChanged: BreakpointStatusChangedWithId): void {
|
||||
const breakpoint = this._idToBreakpoint.get(breakpointStatusChanged.breakpoint.id);
|
||||
this.currentBreakpointsMapping.setAndReplaceIfExist(breakpoint, breakpointStatusChanged.breakpoint);
|
||||
}
|
||||
|
||||
public assertIsVerified(breakpoint: BreakpointWizard): void {
|
||||
|
@ -48,8 +52,12 @@ export class PerformChangesImmediatelyState implements IBreakpointsBatchingStrat
|
|||
await this._breakpointsAssertions.waitUntilVerified(breakpoint);
|
||||
}
|
||||
|
||||
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
|
||||
await this._breakpointsAssertions.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, expectedStackTrace);
|
||||
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
|
||||
await this._breakpointsAssertions.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, verifications);
|
||||
}
|
||||
|
||||
public async assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void> {
|
||||
await this._breakpointsAssertions.assertIsHitThenResume(breakpoint, verifications);
|
||||
}
|
||||
|
||||
private currentBreakpoints(): BreakpointWizard[] {
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import _ = require('lodash');
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { BreakpointWizard } from '../breakpointWizard';
|
||||
import { PromiseOrNot } from 'vscode-chrome-debug-core';
|
||||
import { ValidatedSet } from '../../../core-v2/chrome/collections/validatedSet';
|
||||
import {
|
||||
IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate
|
||||
IBreakpointsBatchingStrategy, InternalFileBreakpointsWizard, CurrentBreakpointsMapping, BreakpointsUpdate, BreakpointStatusChangedWithId
|
||||
} from './internalFileBreakpointsWizard';
|
||||
import { IVerifications } from './breakpointsAssertions';
|
||||
|
||||
export class BatchingUpdatesState implements IBreakpointsBatchingStrategy {
|
||||
private readonly _breakpointsToSet = new ValidatedSet<BreakpointWizard>();
|
||||
private readonly _breakpointsToUnset = new ValidatedSet<BreakpointWizard>();
|
||||
private readonly _actionsToCompleteAfterBatch: (() => PromiseOrNot<void>)[] = [];
|
||||
|
||||
public constructor(private readonly _internal: InternalFileBreakpointsWizard, public readonly currentBreakpointsMapping: CurrentBreakpointsMapping) { }
|
||||
public constructor(private readonly _internal: InternalFileBreakpointsWizard, public readonly currentBreakpointsMapping: CurrentBreakpointsMapping) {}
|
||||
|
||||
public set(breakpointWizard: BreakpointWizard): void {
|
||||
this._breakpointsToSet.add(breakpointWizard);
|
||||
|
@ -32,11 +37,15 @@ export class BatchingUpdatesState implements IBreakpointsBatchingStrategy {
|
|||
this._actionsToCompleteAfterBatch.push(() => this._internal.waitUntilVerified(breakpoint));
|
||||
}
|
||||
|
||||
public onBreakpointStatusChange(_breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
|
||||
public onBreakpointStatusChange(_breakpointStatusChanged: BreakpointStatusChangedWithId): void {
|
||||
throw new Error(`Breakpoint status shouldn't be updated while doing a batch update. Is this happening due to a product or test bug?`);
|
||||
}
|
||||
|
||||
public async assertIsHitThenResumeWhen(_breakpoint: BreakpointWizard, _lastActionToMakeBreakpointHit: () => Promise<void>, _expectedStackTrace: string): Promise<void> {
|
||||
public async assertIsHitThenResumeWhen(_breakpoint: BreakpointWizard, _lastActionToMakeBreakpointHit: () => Promise<void>, _verifications: IVerifications): Promise<void> {
|
||||
throw new Error(`Breakpoint shouldn't be verified while doing a batch update. Is this happening due to a product or test bug?`);
|
||||
}
|
||||
|
||||
public async assertIsHitThenResume(_breakpoint: BreakpointWizard, _verifications: IVerifications): Promise<void> {
|
||||
throw new Error(`Breakpoint shouldn't be verified while doing a batch update. Is this happening due to a product or test bug?`);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,94 +1,98 @@
|
|||
import * as assert from 'assert';
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { expect, use } from 'chai';
|
||||
import * as chaiString from 'chai-string';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { THREAD_ID } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { BreakpointWizard } from '../breakpointWizard';
|
||||
import { InternalFileBreakpointsWizard, CurrentBreakpointsMapping } from './internalFileBreakpointsWizard';
|
||||
import { BreakpointsWizard } from '../breakpointsWizard';
|
||||
import { waitUntilReadyWithTimeout } from '../../../utils/waitUntilReadyWithTimeout';
|
||||
import { isThisV2, isThisV1 } from '../../../testSetup';
|
||||
import { ExpectedFrame, StackTraceObjectAssertions } from './stackTraceObjectAssertions';
|
||||
import { StackTraceStringAssertions } from './stackTraceStringAssertions';
|
||||
import { VariablesWizard, IExpectedVariables } from '../../variables/variablesWizard';
|
||||
import { StackFrameWizard, stackTrace } from '../../variables/stackFrameWizard';
|
||||
|
||||
use(chaiString);
|
||||
|
||||
export interface IVerifications {
|
||||
variables?: IExpectedVariables;
|
||||
stackTrace?: string | ExpectedFrame[];
|
||||
stackFrameFormat?: DebugProtocol.StackFrameFormat;
|
||||
}
|
||||
|
||||
interface IObjectWithLocation {
|
||||
source?: DebugProtocol.Source;
|
||||
line?: number; // One based line number
|
||||
column?: number; // One based colum number
|
||||
}
|
||||
|
||||
export class BreakpointsAssertions {
|
||||
private readonly _variablesWizard = new VariablesWizard(this._internal.client);
|
||||
|
||||
public constructor(
|
||||
private readonly _breakpointsWizard: BreakpointsWizard,
|
||||
private readonly _internal: InternalFileBreakpointsWizard,
|
||||
public readonly currentBreakpointsMapping: CurrentBreakpointsMapping) { }
|
||||
|
||||
public assertIsVerified(breakpoint: BreakpointWizard): void {
|
||||
const breakpointStatus = this.currentBreakpointsMapping.get(breakpoint);
|
||||
assert(breakpointStatus.verified, `Expected ${breakpoint} to be verified yet it wasn't: ${breakpointStatus.message}`);
|
||||
// Convert to one based to match the VS Code potocol and what VS Code does if you try to open that file at that line number
|
||||
// const oneBasedExpectedLineNumber = breakpoint.position.lineNumber + 1;
|
||||
// const oneBasedExpectedColumnNumber = breakpoint.position.columnNumber + 1;
|
||||
// const filePath = this._internal.filePath;
|
||||
|
||||
// TODO: Re-enable this once we figure out how to deal with source-maps that do unexpected things
|
||||
// assert.equal(breakpointStatus.line, oneBasedExpectedLineNumber,
|
||||
// `Expected ${breakpoint} actual line to be ${filePath}:${oneBasedExpectedLineNumber}:${oneBasedExpectedColumnNumber}`
|
||||
// + ` yet it was ${filePath}:${breakpointStatus.line}:${breakpointStatus.column}`);
|
||||
const breakpointStatus = this.currentBreakpointsMapping.get(breakpoint);
|
||||
this.assertLocationMatchesExpected(breakpointStatus, breakpoint);
|
||||
expect(breakpointStatus.verified, `Expected ${breakpoint} to be verified yet it wasn't: ${breakpointStatus.message}`).to.equal(true);
|
||||
}
|
||||
|
||||
public async waitUntilVerified(breakpoint: BreakpointWizard): Promise<void> {
|
||||
await waitUntilReadyWithTimeout(() => this.currentBreakpointsMapping.get(breakpoint).verified);
|
||||
}
|
||||
|
||||
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
|
||||
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
|
||||
const actionResult = lastActionToMakeBreakpointHit();
|
||||
const vsCodeStatus = this.currentBreakpointsMapping.get(breakpoint);
|
||||
const location = { path: this._internal.filePath, line: vsCodeStatus.line, colum: vsCodeStatus.column };
|
||||
|
||||
// TODO: Merge the two following calls together
|
||||
await this._internal.client.assertStoppedLocation('breakpoint', location);
|
||||
await this._breakpointsWizard.assertIsPaused();
|
||||
|
||||
const stackTraceResponse = await this._internal.client.send('stackTrace', {
|
||||
threadId: THREAD_ID,
|
||||
format: {
|
||||
parameters: true,
|
||||
parameterTypes: true,
|
||||
parameterNames: true,
|
||||
line: true,
|
||||
module: true
|
||||
}
|
||||
});
|
||||
|
||||
this.validateStackTraceResponse(stackTraceResponse);
|
||||
|
||||
const formattedExpectedStackTrace = expectedStackTrace.replace(/^\s+/gm, ''); // Remove the white space we put at the start of the lines to make the stack trace align with the code
|
||||
const actualStackTrace = this.extractStackTrace(stackTraceResponse);
|
||||
assert.equal(actualStackTrace, formattedExpectedStackTrace, `Expected the stack trace when hitting ${breakpoint} to be:\n${formattedExpectedStackTrace}\nyet it is:\n${actualStackTrace}`);
|
||||
|
||||
// const scopesResponse = await this._internal.client.scopesRequest({ frameId: stackTraceResponse.body.stackFrames[0].id });
|
||||
/// const scopes = scopesResponse.body.scopes;
|
||||
await this._internal.client.continueRequest();
|
||||
if (isThisV2) {
|
||||
// TODO: Is getting this event on V2 a bug? See: Continued Event at https://microsoft.github.io/debug-adapter-protocol/specification
|
||||
await this._breakpointsWizard.waitUntilJustResumed();
|
||||
}
|
||||
await this.assertIsHitThenResume(breakpoint, verifications);
|
||||
|
||||
await actionResult;
|
||||
}
|
||||
|
||||
private extractStackTrace(stackTraceResponse: DebugProtocol.StackTraceResponse): string {
|
||||
return stackTraceResponse.body.stackFrames.map(f => this.printStackTraceFrame(f)).join('\n');
|
||||
public async assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void> {
|
||||
await this._breakpointsWizard.waitAndConsumePausedEvent(breakpoint);
|
||||
|
||||
const stackTraceFrames = (await stackTrace(this._internal.client, verifications.stackFrameFormat)).stackFrames;
|
||||
|
||||
// Validate that the topFrame is locate in the same place as the breakpoint
|
||||
this.assertLocationMatchesExpected(stackTraceFrames[0], breakpoint);
|
||||
|
||||
if (typeof verifications.stackTrace === 'string') {
|
||||
const assertions = new StackTraceStringAssertions(breakpoint);
|
||||
assertions.assertResponseMatches(stackTraceFrames, verifications.stackTrace);
|
||||
} else if (typeof verifications.stackTrace === 'object') {
|
||||
const assertions = new StackTraceObjectAssertions(this._breakpointsWizard);
|
||||
assertions.assertResponseMatches(stackTraceFrames, verifications.stackTrace);
|
||||
}
|
||||
|
||||
if (verifications.variables !== undefined) {
|
||||
await this._variablesWizard.assertStackFrameVariablesAre(new StackFrameWizard(this._internal.client, stackTraceFrames[0]), verifications.variables);
|
||||
}
|
||||
|
||||
await this._breakpointsWizard.resume();
|
||||
}
|
||||
|
||||
private printStackTraceFrame(frame: DebugProtocol.StackFrame): string {
|
||||
let frameName = frame.name;
|
||||
return `${frameName}:${frame.column}${frame.presentationHint && frame.presentationHint !== 'normal' ? ` (${frame.presentationHint})` : ''}`;
|
||||
}
|
||||
private assertLocationMatchesExpected(objectWithLocation: IObjectWithLocation, breakpoint: BreakpointWizard): void {
|
||||
const expectedFilePath = this._internal.filePath;
|
||||
|
||||
private validateStackTraceResponse(stackTraceResponse: DebugProtocol.StackTraceResponse) {
|
||||
expect(stackTraceResponse.success, `Expected the response to the stack trace request to be succesful yet it failed: ${JSON.stringify(stackTraceResponse)}`).to.equal(true);
|
||||
expect(stackTraceResponse.body.totalFrames, `The number of stackFrames was different than the value supplied on the totalFrames field`)
|
||||
.to.equal(stackTraceResponse.body.stackFrames.length);
|
||||
stackTraceResponse.body.stackFrames.forEach(frame => {
|
||||
// Warning: We don't currently validate frame.source.path
|
||||
const expectedSourceNameAndLine = ` [${frame.source.name}] Line ${frame.line}`;
|
||||
expect(frame.name, 'Expected the formatted name to match the source name and line supplied as individual attributes').to.endsWith(expectedSourceNameAndLine);
|
||||
});
|
||||
expect(objectWithLocation.source).to.not.equal(undefined);
|
||||
expect(objectWithLocation.source!.path!.toLowerCase()).to.be.equal(expectedFilePath.toLowerCase());
|
||||
expect(objectWithLocation.source!.name!.toLowerCase()).to.be.equal(path.basename(expectedFilePath.toLowerCase()));
|
||||
|
||||
const expectedLineNumber = breakpoint.boundPosition.lineNumber + 1;
|
||||
const expectedColumNumber = breakpoint.boundPosition.columnNumber + 1;
|
||||
const expectedBPLocationPrinted = `${expectedFilePath}:${expectedLineNumber}:${expectedColumNumber}`;
|
||||
const actualBPLocationPrinted = `${objectWithLocation.source!.path}:${objectWithLocation.line}:${objectWithLocation.column}`;
|
||||
|
||||
expect(actualBPLocationPrinted.toLowerCase()).to.be.equal(expectedBPLocationPrinted.toLowerCase());
|
||||
}
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { BreakpointsUpdate, StateChanger, InternalFileBreakpointsWizard } from './internalFileBreakpointsWizard';
|
||||
import { BreakpointsUpdate, StateChanger, InternalFileBreakpointsWizard, BreakpointWithId } from './internalFileBreakpointsWizard';
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { BreakpointWizard, VSCodeActionWhenHit } from '../breakpointWizard';
|
||||
import { PerformChangesImmediatelyState } from './performChangesImmediatelyState';
|
||||
import { ValidatedMap } from '../../../core-v2/chrome/collections/validatedMap';
|
||||
import { PauseOnHitCount } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
|
||||
import { PauseOnHitCount, AlwaysPause } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
|
||||
import { BreakpointsWizard } from '../breakpointsWizard';
|
||||
import { Replace } from '../../../core-v2/typeUtils';
|
||||
|
||||
type SetBreakpointsResponseWithId = Replace<DebugProtocol.SetBreakpointsResponse, 'body',
|
||||
Replace<DebugProtocol.SetBreakpointsResponse['body'], 'breakpoints', BreakpointWithId[]>>;
|
||||
|
||||
export class BreakpointsUpdater {
|
||||
public constructor(
|
||||
|
@ -21,17 +30,11 @@ export class BreakpointsUpdater {
|
|||
|
||||
const response = await this._client.setBreakpointsRequest({ breakpoints: vsCodeBps, source: { path: this._internal.filePath } });
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Failed to set the breakpoints for: ${this._internal.filePath}`);
|
||||
}
|
||||
this.validateResponse(response, vsCodeBps);
|
||||
const responseWithIds = <SetBreakpointsResponseWithId>response;
|
||||
|
||||
const expected = vsCodeBps.length;
|
||||
const actual = response.body.breakpoints.length;
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Expected to receive ${expected} breakpoints yet we got ${actual}. Received breakpoints: ${JSON.stringify(response.body.breakpoints)}`);
|
||||
}
|
||||
|
||||
const breakpointToStatus = new ValidatedMap<BreakpointWizard, DebugProtocol.Breakpoint>(_.zip(updatedBreakpoints, response.body.breakpoints));
|
||||
const breakpointToStatus = new ValidatedMap<BreakpointWizard, BreakpointWithId>
|
||||
(<[[BreakpointWizard, BreakpointWithId]]>_.zip(updatedBreakpoints, responseWithIds.body.breakpoints));
|
||||
this._changeState(new PerformChangesImmediatelyState(this._breakpointsWizard, this._internal, breakpointToStatus));
|
||||
}
|
||||
|
||||
|
@ -43,10 +46,29 @@ export class BreakpointsUpdater {
|
|||
}
|
||||
|
||||
private actionWhenHitToVSCodeProtocol(breakpoint: BreakpointWizard): VSCodeActionWhenHit {
|
||||
if (breakpoint.actionWhenHit instanceof PauseOnHitCount) {
|
||||
if (breakpoint.actionWhenHit instanceof AlwaysPause) {
|
||||
return {};
|
||||
} else if (breakpoint.actionWhenHit instanceof PauseOnHitCount) {
|
||||
return { hitCondition: breakpoint.actionWhenHit.pauseOnHitCondition };
|
||||
} else {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
}
|
||||
|
||||
private validateResponse(response: DebugProtocol.SetBreakpointsResponse, vsCodeBps: DebugProtocol.SourceBreakpoint[]): void {
|
||||
if (!response.success) {
|
||||
throw new Error(`Failed to set the breakpoints for: ${this._internal.filePath}`);
|
||||
}
|
||||
|
||||
const expected = vsCodeBps.length;
|
||||
const actual = response.body.breakpoints.length;
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Expected to receive ${expected} breakpoints yet we got ${actual}. Received breakpoints: ${JSON.stringify(response.body.breakpoints)}`);
|
||||
}
|
||||
|
||||
const bpsWithoutId = response.body.breakpoints.filter(bp => bp.id === undefined);
|
||||
if (bpsWithoutId.length !== 0) {
|
||||
throw new Error(`Expected to receive all breakpoints with id yet we got some without ${JSON.stringify(response.body.breakpoints)}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { findPositionOfTextInFile } from '../../../utils/findPositionOfTextInFile';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { PauseOnHitCount } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
|
||||
import { AlwaysPause, IBPActionWhenHit } from '../../../core-v2/chrome/internal/breakpoints/bpActionWhenHit';
|
||||
import { BreakpointWizard } from '../breakpointWizard';
|
||||
import { ValidatedMap } from '../../../core-v2/chrome/collections/validatedMap';
|
||||
import { FileBreakpointsWizard } from '../fileBreakpointsWizard';
|
||||
|
@ -10,6 +15,11 @@ import { BatchingUpdatesState } from './batchingUpdatesState';
|
|||
import { PerformChangesImmediatelyState } from './performChangesImmediatelyState';
|
||||
import { BreakpointsUpdater } from './breakpointsUpdater';
|
||||
import { BreakpointsWizard } from '../breakpointsWizard';
|
||||
import { MakePropertyRequired, Replace } from '../../../core-v2/typeUtils';
|
||||
import { IVerifications } from './breakpointsAssertions';
|
||||
|
||||
export type BreakpointWithId = MakePropertyRequired<DebugProtocol.Breakpoint, 'id'>;
|
||||
export type BreakpointStatusChangedWithId = Replace<DebugProtocol.BreakpointEvent['body'], 'breakpoint', BreakpointWithId>;
|
||||
|
||||
export class BreakpointsUpdate {
|
||||
public constructor(
|
||||
|
@ -26,12 +36,13 @@ export interface IBreakpointsBatchingStrategy {
|
|||
|
||||
waitUntilVerified(breakpoint: BreakpointWizard): Promise<void>;
|
||||
assertIsVerified(breakpoint: BreakpointWizard): void;
|
||||
assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void>;
|
||||
assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void>;
|
||||
assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void>;
|
||||
|
||||
onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void;
|
||||
onBreakpointStatusChange(breakpointStatusChanged: BreakpointStatusChangedWithId): void;
|
||||
}
|
||||
|
||||
export type CurrentBreakpointsMapping = ValidatedMap<BreakpointWizard, DebugProtocol.Breakpoint>;
|
||||
export type CurrentBreakpointsMapping = ValidatedMap<BreakpointWizard, BreakpointWithId>;
|
||||
|
||||
export type StateChanger = (newState: IBreakpointsBatchingStrategy) => void;
|
||||
|
||||
|
@ -42,9 +53,12 @@ export class InternalFileBreakpointsWizard {
|
|||
|
||||
public constructor(private readonly _breakpointsWizard: BreakpointsWizard, public readonly client: ExtendedDebugClient, public readonly filePath: string) { }
|
||||
|
||||
public async hitCountBreakpoint(options: { lineText: string; hitCountCondition: string; name: string }): Promise<BreakpointWizard> {
|
||||
const position = await findPositionOfTextInFile(this.filePath, options.lineText);
|
||||
return new BreakpointWizard(this, position, new PauseOnHitCount(options.hitCountCondition), options.name);
|
||||
public async breakpoint(options: { name: string, text: string, boundText?: string, actionWhenHit?: IBPActionWhenHit}) {
|
||||
const position = await findPositionOfTextInFile(this.filePath, options.text);
|
||||
const boundPosition = options.boundText ? await findPositionOfTextInFile(this.filePath, options.boundText) : position;
|
||||
const actionWhenHit = options.actionWhenHit || new AlwaysPause();
|
||||
|
||||
return new BreakpointWizard(this, position, actionWhenHit, options.name, boundPosition);
|
||||
}
|
||||
|
||||
public async set(breakpointWizard: BreakpointWizard): Promise<void> {
|
||||
|
@ -63,11 +77,15 @@ export class InternalFileBreakpointsWizard {
|
|||
this._state.assertIsVerified(breakpoint);
|
||||
}
|
||||
|
||||
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, expectedStackTrace: string): Promise<void> {
|
||||
return this._state.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, expectedStackTrace);
|
||||
public async assertIsHitThenResumeWhen(breakpoint: BreakpointWizard, lastActionToMakeBreakpointHit: () => Promise<void>, verifications: IVerifications): Promise<void> {
|
||||
return this._state.assertIsHitThenResumeWhen(breakpoint, lastActionToMakeBreakpointHit, verifications);
|
||||
}
|
||||
|
||||
public onBreakpointStatusChange(breakpointStatusChanged: DebugProtocol.BreakpointEvent): void {
|
||||
public async assertIsHitThenResume(breakpoint: BreakpointWizard, verifications: IVerifications): Promise<void> {
|
||||
return this._state.assertIsHitThenResume(breakpoint, verifications);
|
||||
}
|
||||
|
||||
public onBreakpointStatusChange(breakpointStatusChanged: BreakpointStatusChangedWithId): void {
|
||||
this._state.onBreakpointStatusChange(breakpointStatusChanged);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*---------------------------------------------------------
|
||||
* Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
*--------------------------------------------------------*/
|
||||
|
||||
/** Remove the whitespaces from the start of each line and any comments we find at the end */
|
||||
export function trimWhitespaceAndComments(printedTestInput: string): string {
|
||||
return printedTestInput.replace(/^\s+/gm, '').replace(/ ?\/\/.*$/gm, ''); // Remove the white space we put at the start of the lines to make the printed test input align with the code
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import * as testSetup from '../../../testSetup';
|
||||
import { expect } from 'chai';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { BreakpointsWizard } from '../breakpointsWizard';
|
||||
import { URL } from 'url';
|
||||
|
||||
export interface ExpectedSource {
|
||||
fileRelativePath?: string;
|
||||
url?: URL;
|
||||
evalCode?: boolean;
|
||||
}
|
||||
|
||||
export interface ExpectedFrame {
|
||||
name: string | RegExp;
|
||||
line?: number;
|
||||
column?: number;
|
||||
source?: ExpectedSource;
|
||||
presentationHint?: string;
|
||||
}
|
||||
|
||||
export class StackTraceObjectAssertions {
|
||||
private readonly _projectRoot: string;
|
||||
|
||||
public constructor(breakpointsWizard: BreakpointsWizard) {
|
||||
this._projectRoot = breakpointsWizard.project.props.projectRoot;
|
||||
}
|
||||
|
||||
private assertSourceMatches(actual: DebugProtocol.Source | undefined, expected: ExpectedSource | undefined, index: number) {
|
||||
if (actual == null && expected == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expected == null) {
|
||||
assert.fail(`Source was returned for frame ${index} but none was expected`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (actual == null) {
|
||||
assert.fail(`Source was expected for frame ${index} but none was returned`);
|
||||
return;
|
||||
}
|
||||
|
||||
let expectedName: string;
|
||||
let expectedPath: string;
|
||||
|
||||
if (expected.fileRelativePath) {
|
||||
// Generate the expected path from the relative path and the project root
|
||||
expectedPath = path.join(this._projectRoot, expected.fileRelativePath);
|
||||
expectedName = path.parse(expectedPath).base;
|
||||
} else if (expected.url) {
|
||||
expectedName = expected.url.host;
|
||||
expectedPath = expected.url.toString();
|
||||
} else if (expected.evalCode === true) {
|
||||
// Eval code has source that looks like 'VM123'. Check it by regex instead.
|
||||
expect(actual.name).to.match(/.*VM.*/, `Frame ${index} source name`);
|
||||
expect(actual.path).to.match(/.*VM.*/, `Frame ${index} source path`);
|
||||
return;
|
||||
} else {
|
||||
assert.fail('Not enough information for expected source: set either "fileRelativePath" or "urlRelativePath" or "eval"');
|
||||
return;
|
||||
}
|
||||
|
||||
expect(actual.name).to.equal(expectedName, `Frame ${index} source name`);
|
||||
expect(actual.path).to.equal(expectedPath, `Frame ${index} source path`);
|
||||
}
|
||||
|
||||
private assertFrameMatches(actual: DebugProtocol.StackFrame, expected: ExpectedFrame, index: number) {
|
||||
if (typeof expected.name === 'string') {
|
||||
expect(actual.name).to.equal(expected.name, `Frame ${index} name`);
|
||||
} else if (expected.name instanceof RegExp) {
|
||||
expect(actual.name).to.match(expected.name, `Frame ${index} name`);
|
||||
}
|
||||
|
||||
expect(actual.line).to.equal(expected.line, `Frame ${index} line`);
|
||||
expect(actual.column).to.equal(expected.column, `Frame ${index} column`);
|
||||
|
||||
// Normal V1 stack frames will have no presentationHint, normal V2 stack frames will have presentationHint 'normal'
|
||||
if (testSetup.isThisV1 && expected.presentationHint === 'normal') {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(actual.presentationHint, `Frame ${index} presentationHint`).to.be.undefined;
|
||||
} else {
|
||||
expect(actual.presentationHint).to.equal(expected.presentationHint, `Frame ${index} presentationHint`);
|
||||
}
|
||||
|
||||
this.assertSourceMatches(actual.source, expected.source, index);
|
||||
}
|
||||
|
||||
private assertResponseMatchesFrames(actualFrames: DebugProtocol.StackFrame[], expectedFrames: ExpectedFrame[]) {
|
||||
// Check array length
|
||||
expect(actualFrames.length).to.equal(expectedFrames.length, 'Number of stack frames');
|
||||
|
||||
// Check each frame
|
||||
actualFrames.forEach((actualFrame, i) => {
|
||||
this.assertFrameMatches(actualFrame, expectedFrames[i], i);
|
||||
});
|
||||
}
|
||||
|
||||
public assertResponseMatches(stackTraceFrames: DebugProtocol.StackFrame[], expectedFrames: ExpectedFrame[]) {
|
||||
try {
|
||||
this.assertResponseMatchesFrames(stackTraceFrames, expectedFrames);
|
||||
} catch (e) {
|
||||
const error: assert.AssertionError = e;
|
||||
error.message += '\nActual stack trace response: \n' + JSON.stringify(stackTraceFrames, null, 2);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { expect } from 'chai';
|
||||
import { findLineNumber } from '../../../utils/findPositionOfTextInFile';
|
||||
import { BreakpointWizard } from '../breakpointWizard';
|
||||
import { trimWhitespaceAndComments } from './printedTestInputl';
|
||||
|
||||
export class StackTraceStringAssertions {
|
||||
public constructor(
|
||||
private readonly _breakpoint: BreakpointWizard) { }
|
||||
|
||||
public assertResponseMatches(stackTraceFrames: DebugProtocol.StackFrame[], expectedString: string) {
|
||||
|
||||
stackTraceFrames.forEach(frame => {
|
||||
// Warning: We don't currently validate frame.source.path
|
||||
expect(frame.source).not.to.equal(undefined);
|
||||
const expectedSourceNameAndLine = ` [${frame.source!.name}] Line ${frame.line}`;
|
||||
expect(frame.name, 'Expected the formatted name to match the source name and line supplied as individual attributes').to.endsWith(expectedSourceNameAndLine);
|
||||
});
|
||||
|
||||
|
||||
const formattedExpectedStackTrace = trimWhitespaceAndComments(expectedString);
|
||||
this.applyIgnores(formattedExpectedStackTrace, stackTraceFrames);
|
||||
const actualStackTrace = this.extractStackTrace(stackTraceFrames);
|
||||
assert.equal(actualStackTrace, formattedExpectedStackTrace, `Expected the stack trace when hitting ${this._breakpoint} to be:\n${formattedExpectedStackTrace}\nyet it is:\n${actualStackTrace}`);
|
||||
}
|
||||
|
||||
private applyIgnores(formattedExpectedStackTrace: string, stackTrace: DebugProtocol.StackFrame[]): void {
|
||||
const ignoreFunctionNameText = '<__IGNORE_FUNCTION_NAME__>';
|
||||
const ignoreFunctionName = findLineNumber(formattedExpectedStackTrace, formattedExpectedStackTrace.indexOf(ignoreFunctionNameText));
|
||||
if (ignoreFunctionName >= 0) {
|
||||
expect(stackTrace.length).to.be.greaterThan(ignoreFunctionName);
|
||||
const ignoredFrame = stackTrace[ignoreFunctionName];
|
||||
ignoredFrame.name = `${ignoreFunctionNameText} [${ignoredFrame.source!.name}] Line ${ignoredFrame.line}`;
|
||||
}
|
||||
}
|
||||
|
||||
private extractStackTrace(stackTrace: DebugProtocol.StackFrame[]): string {
|
||||
return stackTrace.map(f => this.printStackTraceFrame(f)).join('\n');
|
||||
}
|
||||
|
||||
private printStackTraceFrame(frame: DebugProtocol.StackFrame): string {
|
||||
let frameName = frame.name;
|
||||
return `${frameName}:${frame.column}${frame.presentationHint && frame.presentationHint !== 'normal' ? ` (${frame.presentationHint})` : ''}`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { ExtendedDebugClient, THREAD_ID } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
import { utils } from 'vscode-chrome-debug-core';
|
||||
import { isThisV2 } from '../testSetup';
|
||||
import { waitUntilReadyWithTimeout } from '../utils/waitUntilReadyWithTimeout';
|
||||
import { expect } from 'chai';
|
||||
import { ValidatedMap } from '../core-v2/chrome/collections/validatedMap';
|
||||
import { wrapWithMethodLogger } from '../core-v2/chrome/logging/methodsCalledLogger';
|
||||
|
||||
enum EventToConsume {
|
||||
Paused,
|
||||
Resumed,
|
||||
None
|
||||
}
|
||||
|
||||
/** Helper methods to wait and/or verify when the debuggee was paused for any kind of pause.
|
||||
*
|
||||
* Warning: Needs to be created before the debuggee is launched to capture all events and avoid race conditions
|
||||
*/
|
||||
export class PausedWizard {
|
||||
private _noMoreEventsExpected = false;
|
||||
private _eventsToBeConsumed: (DebugProtocol.ContinuedEvent | DebugProtocol.StoppedEvent)[] = [];
|
||||
private static _clientToPausedWizard = new ValidatedMap<ExtendedDebugClient, PausedWizard>();
|
||||
|
||||
private constructor(private readonly _client: ExtendedDebugClient) {
|
||||
this._client.on('stopped', stopped => this.onEvent(stopped));
|
||||
this._client.on('continued', continued => this.onEvent(continued));
|
||||
}
|
||||
|
||||
private onEvent(continued: any) {
|
||||
this.validateNoMoreEventsIfSet(continued);
|
||||
this._eventsToBeConsumed.push(continued);
|
||||
this.logState();
|
||||
}
|
||||
|
||||
// The PausedWizard logic will break if we create 2 PausedWizards for the same client. So we warranty we only create one
|
||||
public static forClient(client: ExtendedDebugClient): PausedWizard {
|
||||
return this._clientToPausedWizard.getOrAdd(client, () => wrapWithMethodLogger(new PausedWizard(client)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the debuggee is not paused
|
||||
*
|
||||
* @param millisecondsToWaitForPauses How much time to wait for pauses
|
||||
*/
|
||||
public async waitAndConsumeResumedEvent(): Promise<void> {
|
||||
await waitUntilReadyWithTimeout(() => this.nextEventToConsume === EventToConsume.Resumed);
|
||||
this.markNextEventAsConsumed('continued');
|
||||
}
|
||||
|
||||
/** Return whether the debuggee is currently paused */
|
||||
public isPaused(): boolean {
|
||||
return this.nextEventToConsume === EventToConsume.Paused;
|
||||
}
|
||||
|
||||
/** Wait and block until the debuggee is paused on a debugger statement */
|
||||
public async waitUntilPausedOnDebuggerStatement(): Promise<void> {
|
||||
return this.waitAndConsumePausedEvent(pauseInfo => {
|
||||
expect(pauseInfo.description).to.equal('Paused on debugger statement');
|
||||
expect(pauseInfo.reason).to.equal('debugger_statement');
|
||||
});
|
||||
}
|
||||
|
||||
/** Wait and block until the debuggee is paused, and then perform the specified action with the pause event's body */
|
||||
public async waitAndConsumePausedEvent(actionWithPausedInfo: (pausedInfo: DebugProtocol.StoppedEvent['body']) => void): Promise<void> {
|
||||
await waitUntilReadyWithTimeout(() => this.nextEventToConsume === EventToConsume.Paused);
|
||||
const pausedEvent = <DebugProtocol.StoppedEvent>this._eventsToBeConsumed[0];
|
||||
this.markNextEventAsConsumed('stopped');
|
||||
actionWithPausedInfo(pausedEvent.body);
|
||||
}
|
||||
|
||||
/** Wait and block until the debuggee has been resumed */
|
||||
public async waitUntilResumed(): Promise<void> {
|
||||
// We assume that nobody is consuming events in parallel, so if we start paused, the wait call won't ever succeed
|
||||
expect(this.nextEventToConsume).to.not.equal(EventToConsume.Paused);
|
||||
|
||||
await waitUntilReadyWithTimeout(() => this.nextEventToConsume === EventToConsume.Resumed);
|
||||
|
||||
this.markNextEventAsConsumed('continued');
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct the debuggee to resume, and verify that the Debug-Adapter sends the proper notification after that happens
|
||||
*/
|
||||
public async resume(): Promise<void> {
|
||||
await this._client.continueRequest();
|
||||
if (isThisV2) {
|
||||
// TODO: Is getting this event on V2 a bug? See: Continued Event at https://microsoft.github.io/debug-adapter-protocol/specification
|
||||
await this.waitUntilResumed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct the debuggee to pause, and verify that the Debug-Adapter sends the proper notification after that happens
|
||||
*/
|
||||
public async pause(): Promise<void> {
|
||||
await this._client.pauseRequest({ threadId: THREAD_ID });
|
||||
|
||||
await this.waitAndConsumePausedEvent(event => {
|
||||
expect(event.reason).to.equal('pause');
|
||||
expect(event.description).to.equal('Paused on user request');
|
||||
});
|
||||
}
|
||||
|
||||
public async waitAndAssertNoMoreEvents(): Promise<void> {
|
||||
expect(this.nextEventToConsume).to.equal(EventToConsume.None);
|
||||
this._noMoreEventsExpected = true;
|
||||
|
||||
// Wait some time, to see if any events appear eventually
|
||||
await utils.promiseTimeout(undefined, 500);
|
||||
|
||||
expect(this.nextEventToConsume).to.equal(EventToConsume.None);
|
||||
}
|
||||
|
||||
private validateNoMoreEventsIfSet(event: DebugProtocol.ContinuedEvent | DebugProtocol.StoppedEvent): void {
|
||||
if (this._noMoreEventsExpected) {
|
||||
throw new Error(`Received an event after it was signaled that no more events were expected: ${JSON.stringify(event)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private logState() {
|
||||
logger.log(`Resume/Pause #events = ${this._eventsToBeConsumed.length}, state = ${EventToConsume[this.nextEventToConsume]}`);
|
||||
}
|
||||
|
||||
private get nextEventToConsume(): EventToConsume {
|
||||
if (this._eventsToBeConsumed.length === 0) {
|
||||
return EventToConsume.None;
|
||||
} else {
|
||||
const nextEventToBeConsumed = this._eventsToBeConsumed[0];
|
||||
switch (nextEventToBeConsumed.event) {
|
||||
case 'stopped':
|
||||
return EventToConsume.Paused;
|
||||
case 'continued':
|
||||
return EventToConsume.Resumed;
|
||||
default:
|
||||
throw new Error(`Expected the event to be consumed to be either a stopped or continued yet it was: ${JSON.stringify(nextEventToBeConsumed)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private markNextEventAsConsumed(eventName: 'continued' | 'stopped'): void {
|
||||
expect(this._eventsToBeConsumed).length.to.be.greaterThan(0);
|
||||
expect(this._eventsToBeConsumed[0].event).to.equal(eventName);
|
||||
this._eventsToBeConsumed.shift();
|
||||
this.logState();
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return 'PausedWizard';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export type ManyVariablesPropertiesPrinted = string; // `${variable.name} = ${variable.value} ${(variable.type)}\n`
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { expect } from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { THREAD_ID, ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { VariablesScopeName } from './variablesWizard';
|
||||
import { ValidatedSet, IValidatedSet } from '../../core-v2/chrome/collections/validatedSet';
|
||||
import { singleElementOfArray } from '../../core-v2/chrome/collections/utilities';
|
||||
import { logger } from 'vscode-debugadapter';
|
||||
interface IVariablesOfScope {
|
||||
scopeName: VariablesScopeName;
|
||||
variables: DebugProtocol.Variable[];
|
||||
}
|
||||
|
||||
const defaultStackFrameFormat: DebugProtocol.StackFrameFormat = {
|
||||
parameters: true,
|
||||
parameterTypes: true,
|
||||
parameterNames: true,
|
||||
line: true,
|
||||
module: true
|
||||
};
|
||||
|
||||
export async function stackTrace(client: ExtendedDebugClient, optionalStackFrameFormat?: DebugProtocol.StackFrameFormat): Promise<DebugProtocol.StackTraceResponse['body']> {
|
||||
const stackFrameFormat = _.defaultTo(optionalStackFrameFormat, defaultStackFrameFormat);
|
||||
const stackTraceResponse = await client.send('stackTrace', { threadId: THREAD_ID, format: stackFrameFormat });
|
||||
expect(stackTraceResponse.success, `Expected the response to the stack trace request to be succesful yet it failed: ${JSON.stringify(stackTraceResponse)}`).to.equal(true);
|
||||
|
||||
// Check totalFrames property
|
||||
expect(stackTraceResponse.body.totalFrames).to.equal(stackTraceResponse.body.stackFrames.length, 'body.totalFrames');
|
||||
return stackTraceResponse.body;
|
||||
}
|
||||
|
||||
export async function topStackFrame(client: ExtendedDebugClient, optionalStackFrameFormat?: DebugProtocol.StackFrameFormat): Promise<DebugProtocol.StackFrame> {
|
||||
const stackFrames = (await stackTrace(client, optionalStackFrameFormat)).stackFrames;
|
||||
expect(stackFrames.length).to.be.greaterThan(0);
|
||||
return stackFrames[0];
|
||||
}
|
||||
|
||||
/** Utility functions to operate on the stack straces and stack frames of the debuggee.
|
||||
* It also provides utilities to access the scopes available in a particular stack frame.
|
||||
*/
|
||||
export class StackFrameWizard {
|
||||
public constructor(private readonly _client: ExtendedDebugClient, private readonly _stackFrame: DebugProtocol.StackFrame) { }
|
||||
|
||||
/** Return a Wizard to interact with the top stack frame of the debuggee of the client */
|
||||
public static async topStackFrame(client: ExtendedDebugClient): Promise<StackFrameWizard> {
|
||||
return new StackFrameWizard(client, await topStackFrame(client));
|
||||
}
|
||||
|
||||
/** Return the variables information for the scopes selected by name */
|
||||
public async variablesOfScopes(manyScopeNames: VariablesScopeName[]): Promise<IVariablesOfScope[]> {
|
||||
const scopes = await this.scopesByNames(manyScopeNames);
|
||||
return Promise.all(scopes.map(async scope => {
|
||||
const variablesResponse = await this._client.variablesRequest({ variablesReference: scope!.variablesReference });
|
||||
expect(variablesResponse.success).to.equal(true);
|
||||
expect(variablesResponse.body).not.to.equal(undefined);
|
||||
const variables = variablesResponse.body.variables;
|
||||
expect(variables).not.to.equal(undefined);
|
||||
return { scopeName: <VariablesScopeName>scope.name.toLowerCase(), variables };
|
||||
}));
|
||||
}
|
||||
|
||||
private async scopesByNames(manyScopeNames: VariablesScopeName[]): Promise<DebugProtocol.Scope[]> {
|
||||
const scopeNamesSet = new ValidatedSet(manyScopeNames.map(name => name.toLowerCase()));
|
||||
const requestedScopes = (await this.scopes()).filter(scope => scopeNamesSet.has(scope.name.toLowerCase()));
|
||||
expect(requestedScopes).to.have.lengthOf(manyScopeNames.length);
|
||||
return requestedScopes;
|
||||
}
|
||||
|
||||
/** Return all the scopes available in the underlying stack frame */
|
||||
public async scopes(): Promise<DebugProtocol.Scope[]> {
|
||||
const scopesResponse = await this._client.scopesRequest({ frameId: this._stackFrame.id });
|
||||
logger.log(`Scopes: ${scopesResponse.body.scopes.map(s => s.name).join(', ')}`);
|
||||
return scopesResponse.body.scopes;
|
||||
}
|
||||
|
||||
/** Return the names of all the global variables in the underlying stack frame */
|
||||
public async globalVariableNames(): Promise<IValidatedSet<string>> {
|
||||
const existingGlobalVariables = await this.variablesOfScope('global');
|
||||
return new ValidatedSet(existingGlobalVariables.map(variable => variable.name));
|
||||
}
|
||||
|
||||
/** Return the variables information for a particular scope of the underlying stack frame */
|
||||
public async variablesOfScope(scopeName: VariablesScopeName): Promise<DebugProtocol.Variable[]> {
|
||||
return singleElementOfArray(await this.variablesOfScopes([scopeName])).variables;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface IVariableInformation {
|
||||
name: string;
|
||||
value: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a collection of variable informations to make it easier to compare
|
||||
* the expected variables of a test, and the actual variables of the debuggee
|
||||
*/
|
||||
export function printVariables(variables: IVariableInformation[]): string {
|
||||
const variablesPrinted = variables.map(variable => printVariable(variable));
|
||||
return variablesPrinted.join('\n');
|
||||
}
|
||||
|
||||
function printVariable(variable: IVariableInformation): string {
|
||||
return `${variable.name} = ${variable.value} (${(variable.type)})`;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { expect } from 'chai';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { trimWhitespaceAndComments } from '../breakpoints/implementation/printedTestInputl';
|
||||
import { ManyVariablesValues, } from './variablesWizard';
|
||||
import { printVariables } from './variablesPrinting';
|
||||
|
||||
/** Whether the expected variables should match exactly the actual variables of the debuggee
|
||||
* or whether the expected variables should only be a subset of the actual variables of the debuggee
|
||||
*/
|
||||
export enum KindOfVerification {
|
||||
SameAndExact, /** Same exact variables */
|
||||
ProperSubset /** Expected variables are a subset of the actual variables */
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide methods to validate that the variables appearing on the stack trace are what we expect
|
||||
*/
|
||||
export class VariablesVerifier {
|
||||
/** Verify that the actual variables are exactly the variables that we expect */
|
||||
public assertVariablesAre(variables: DebugProtocol.Variable[], expectedVariables: string | ManyVariablesValues): void {
|
||||
if (typeof expectedVariables === 'string') {
|
||||
this.assertVariablesPrintedAre(variables, expectedVariables);
|
||||
} else {
|
||||
this.assertVariablesValuesAre(variables, expectedVariables);
|
||||
}
|
||||
}
|
||||
|
||||
private assertVariablesPrintedAre(variables: DebugProtocol.Variable[], expectedVariablesPrinted: string): void {
|
||||
const trimmedVariables = trimWhitespaceAndComments(expectedVariablesPrinted);
|
||||
expect(printVariables(variables)).to.equal(trimmedVariables);
|
||||
}
|
||||
|
||||
private assertVariablesValuesAre(manyVariables: DebugProtocol.Variable[], expectedVariablesValues: ManyVariablesValues): void {
|
||||
return this.assertVariablesValuesSatisfy(manyVariables, expectedVariablesValues, KindOfVerification.SameAndExact);
|
||||
}
|
||||
|
||||
/** Verify that the actual variables include as a proper subset the variables that we expect */
|
||||
public assertVariablesValuesContain(manyVariables: DebugProtocol.Variable[], expectedVariablesValues: ManyVariablesValues): void {
|
||||
return this.assertVariablesValuesSatisfy(manyVariables, expectedVariablesValues, KindOfVerification.ProperSubset);
|
||||
}
|
||||
|
||||
/** Verify that the actual variables match the expected variables with the verification specified as a parameter (Same or subset) */
|
||||
public assertVariablesValuesSatisfy(
|
||||
manyVariables: DebugProtocol.Variable[], expectedVariablesValues: ManyVariablesValues,
|
||||
kindOfVerification: KindOfVerification): void {
|
||||
const actualVariableNames = manyVariables.map(variable => variable.name);
|
||||
const expectedVariablesNames = Object.keys(expectedVariablesValues);
|
||||
switch (kindOfVerification) {
|
||||
case KindOfVerification.ProperSubset:
|
||||
expect(actualVariableNames).to.contain.members(expectedVariablesNames);
|
||||
break;
|
||||
case KindOfVerification.SameAndExact:
|
||||
expect(actualVariableNames).to.have.members(expectedVariablesNames);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected comparison algorithm: ${kindOfVerification}`);
|
||||
}
|
||||
|
||||
for (const variable of manyVariables) {
|
||||
const variableName = variable.name;
|
||||
if (expectedVariablesNames.indexOf(variableName) >= 0) {
|
||||
const expectedValue = expectedVariablesValues[variableName];
|
||||
expect(expectedValue).to.not.equal(undefined);
|
||||
expect(variable!.evaluateName).to.be.equal(variable!.name); // Is this ever different?
|
||||
expect(variable!.variablesReference).to.be.greaterThan(-1);
|
||||
expect(variable!.value).to.be.equal(`${expectedValue}`);
|
||||
// TODO: Validate variable type too
|
||||
} else {
|
||||
expect(kindOfVerification).to.equal(KindOfVerification.ProperSubset); // This should not happen for same elements
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { ExtendedDebugClient } from 'vscode-chrome-debug-core-testsupport';
|
||||
import { PromiseOrNot } from 'vscode-chrome-debug-core';
|
||||
import { StackFrameWizard } from './stackFrameWizard';
|
||||
import { VariablesVerifier } from './variablesVerifier';
|
||||
import { ValidatedMap } from '../../core-v2/chrome/collections/validatedMap';
|
||||
import { trimWhitespaceAndComments } from '../breakpoints/implementation/printedTestInputl';
|
||||
import { expect } from 'chai';
|
||||
import { printVariables } from './variablesPrinting';
|
||||
|
||||
export interface VariablePrintedProperties {
|
||||
value: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface ManyVariablePrintedProperties {
|
||||
[variableName: string]: VariablePrintedProperties;
|
||||
}
|
||||
|
||||
export interface ManyVariablesValues {
|
||||
[variableName: string]: unknown;
|
||||
}
|
||||
|
||||
export type ManyVariablesPropertiesPrinted = string; // `${variable.name} = ${variable.value} ${(variable.type)}\n`
|
||||
|
||||
export type IScopeExpectedVariables = ManyVariablesPropertiesPrinted | ManyVariablesValues;
|
||||
|
||||
export interface IExpectedVariables {
|
||||
script?: IScopeExpectedVariables;
|
||||
local?: IScopeExpectedVariables;
|
||||
global?: IScopeExpectedVariables;
|
||||
catch?: IScopeExpectedVariables;
|
||||
block?: IScopeExpectedVariables;
|
||||
closure?: IScopeExpectedVariables;
|
||||
eval?: IScopeExpectedVariables;
|
||||
with?: IScopeExpectedVariables;
|
||||
module?: IScopeExpectedVariables;
|
||||
|
||||
local_contains?: ManyVariablesValues;
|
||||
}
|
||||
|
||||
export type VariablesScopeName = keyof IExpectedVariables;
|
||||
export type VerificationModifier = 'contains' | '';
|
||||
|
||||
export class VariablesWizard {
|
||||
public constructor(private readonly _client: ExtendedDebugClient) { }
|
||||
|
||||
/** Verify that the global variables have the expected values, ignoring the variables in <namesOfGlobalsToIgnore> */
|
||||
public async assertNewGlobalVariariablesAre(actionThatAddsNewVariables: () => PromiseOrNot<void>, expectedGlobals: ManyVariablesPropertiesPrinted): Promise<void> {
|
||||
// Store pre-existing global variables' names
|
||||
const namesOfGlobalsToIgnore = await (await this.topStackFrameHelper()).globalVariableNames();
|
||||
|
||||
// Perform an action that adds new global variables
|
||||
await actionThatAddsNewVariables();
|
||||
|
||||
const globalsOnFrame = await (await this.topStackFrameHelper()).variablesOfScope('global');
|
||||
const nonIgnoredGlobals = globalsOnFrame.filter(global => !namesOfGlobalsToIgnore.has(global.name));
|
||||
const expectedGlobalsTrimmed = trimWhitespaceAndComments(expectedGlobals);
|
||||
expect(printVariables(nonIgnoredGlobals)).to.equal(expectedGlobalsTrimmed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the stackFrame contains some variables with a specific value
|
||||
*/
|
||||
public async assertTopFrameVariablesAre(verifications: IExpectedVariables): Promise<void> {
|
||||
await this.assertStackFrameVariablesAre(await this.topStackFrameHelper(), verifications);
|
||||
}
|
||||
|
||||
public async assertStackFrameVariablesAre(stackFrame: StackFrameWizard, verifications: IExpectedVariables) {
|
||||
const scopesWithModifiers = Object.keys(verifications);
|
||||
const scopesWithoutModifiers = scopesWithModifiers.map(s => this.splitIntoScopeNameAndModifier(s)[0]);
|
||||
const withoutModifiersToWith = new ValidatedMap(_.zip(scopesWithoutModifiers, scopesWithModifiers));
|
||||
const manyScopes = await (stackFrame).variablesOfScopes(scopesWithoutModifiers);
|
||||
for (const scope of manyScopes) {
|
||||
const scopeNameWithModifier = withoutModifiersToWith.get(scope.scopeName)!;
|
||||
const [, modifier] = this.splitIntoScopeNameAndModifier(scopeNameWithModifier);
|
||||
switch (modifier) {
|
||||
case '':
|
||||
this.verificator.assertVariablesAre(scope.variables, verifications[scopeNameWithModifier]!);
|
||||
break;
|
||||
case 'contains':
|
||||
this.verificator.assertVariablesValuesContain(scope.variables, <ManyVariablesValues>verifications[scopeNameWithModifier]!);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown modified used for variables verification: ${modifier} in ${scopeNameWithModifier}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private splitIntoScopeNameAndModifier(modifiedScopeName: keyof IExpectedVariables): [VariablesScopeName, VerificationModifier] {
|
||||
const components = modifiedScopeName.split('_');
|
||||
if (components.length > 2) {
|
||||
throw new Error(`Invalid modified scope name: ${modifiedScopeName}`);
|
||||
}
|
||||
|
||||
return [<VariablesScopeName>components[0], <VerificationModifier>_.defaultTo(components[1], '')];
|
||||
}
|
||||
|
||||
private get verificator(): VariablesVerifier {
|
||||
return new VariablesVerifier();
|
||||
}
|
||||
|
||||
private async topStackFrameHelper(): Promise<StackFrameWizard> {
|
||||
return await StackFrameWizard.topStackFrame(this._client);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
import * as path from 'path';
|
||||
import * as mockery from 'mockery';
|
||||
import { execSync } from 'child_process';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
|
||||
export function setupUnhandledRejectionListener(): void {
|
||||
process.addListener('unhandledRejection', unhandledRejectionListener);
|
||||
|
@ -13,7 +15,7 @@ export function removeUnhandledRejectionListener(): void {
|
|||
process.removeListener('unhandledRejection', unhandledRejectionListener);
|
||||
}
|
||||
|
||||
function unhandledRejectionListener(reason: any, p: Promise<any>) {
|
||||
function unhandledRejectionListener(reason: any, _p: Promise<any>) {
|
||||
console.log('*');
|
||||
console.log('**');
|
||||
console.log('***');
|
||||
|
@ -49,6 +51,23 @@ export function registerLocMocks(): void {
|
|||
});
|
||||
}
|
||||
|
||||
function dummyLocalize(id: string, englishString: string): string {
|
||||
/**
|
||||
* Kills all running instances of chrome (that were launched by the tests, on Windows at least) on the test host
|
||||
*/
|
||||
export function killAllChrome() {
|
||||
try {
|
||||
const killCmd = (process.platform === 'win32') ? `start powershell -WindowStyle hidden -Command "Get-Process | Where-Object {$_.Path -like '*${puppeteer.executablePath()}*'} | Stop-Process"` : 'killall chrome';
|
||||
const hideWindows = process.env['TEST_DA_HIDE_WINDOWS'] === 'true';
|
||||
const output = execSync(killCmd, { windowsHide: hideWindows }); // TODO: windowsHide paramenter doesn't currently work. It might be related to this: https://github.com/nodejs/node/issues/21825
|
||||
if (output.length > 0) { // Don't print empty lines
|
||||
console.log(output.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error killing chrome: ${e.message}`);
|
||||
}
|
||||
// the kill command will exit with a non-zero code (and cause execSync to throw) if chrome is already stopped
|
||||
}
|
||||
|
||||
function dummyLocalize(_id: string, englishString: string): string {
|
||||
return englishString;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome",
|
||||
"trace": "verbose",
|
||||
//"url": "http://localhost:5000/other.html",
|
||||
"file": "C:/development/debug_adapters_v2/vscode-chrome-debug/testdata/inline_scripts/other.html",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "my-chrome",
|
||||
"request": "launch",
|
||||
"name": "My Launch Chrome",
|
||||
"url": "http://localhost:5000/other.html",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
console.log('first script of the day!');
|
||||
|
||||
function blub2() {
|
||||
console.log('EAC before');
|
||||
let a = 0;
|
||||
console.log('EAC after')
|
||||
let b = 2;
|
||||
let c = a + b; // bpLabel: inlineScript1
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<input type="button" value="blub" id="actionButton" onclick="blub2()">
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
function blub3() {
|
||||
console.log('EAC before');
|
||||
let a = 0;
|
||||
console.log('EAC after')
|
||||
let b = 2;
|
||||
let c = a + b; // bpLabel: inlineScript2
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
function blub4() {
|
||||
console.log('EAC before');
|
||||
let a = 0;
|
||||
console.log('EAC after')
|
||||
let b = 2;
|
||||
let c = a + b; // bpLabel: inlineScript3
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<input type="button" value="blub" id="actionButton" onclick="blub4()">
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
console.log('first script of the day!');
|
||||
|
||||
function blub2() {
|
||||
console.log('EAC before');
|
||||
let a = 0;
|
||||
console.log('EAC after')
|
||||
let b = 2;
|
||||
let c = a + b; // bpLabel: inlineScriptSingle1
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<input type="button" value="blub" id="actionButton" onclick="blub2()">
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5000",
|
||||
"webRoot": "${workspaceFolder}/dist"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
Test project for the React framework, created using [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
Can re-generate the project output with
|
||||
### `npm run build`
|
|
@ -0,0 +1,93 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Make sure that including paths.js after env.js will read .env variables.
|
||||
delete require.cache[require.resolve('./paths')];
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
throw new Error(
|
||||
'The NODE_ENV environment variable is required but was not specified.'
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||
var dotenvFiles = [
|
||||
`${paths.dotenv}.${NODE_ENV}.local`,
|
||||
`${paths.dotenv}.${NODE_ENV}`,
|
||||
// Don't include `.env.local` for `test` environment
|
||||
// since normally you expect tests to produce the same
|
||||
// results for everyone
|
||||
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
|
||||
paths.dotenv,
|
||||
].filter(Boolean);
|
||||
|
||||
// Load environment variables from .env* files. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set. Variable expansion is supported in .env files.
|
||||
// https://github.com/motdotla/dotenv
|
||||
// https://github.com/motdotla/dotenv-expand
|
||||
dotenvFiles.forEach(dotenvFile => {
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
require('dotenv-expand')(
|
||||
require('dotenv').config({
|
||||
path: dotenvFile,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We support resolving modules according to `NODE_PATH`.
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
// https://github.com/facebook/create-react-app/issues/253.
|
||||
// It works similar to `NODE_PATH` in Node itself:
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
|
||||
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
|
||||
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
|
||||
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
|
||||
// We also resolve them to make sure all tools using them work consistently.
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
process.env.NODE_PATH = (process.env.NODE_PATH || '')
|
||||
.split(path.delimiter)
|
||||
.filter(folder => folder && !path.isAbsolute(folder))
|
||||
.map(folder => path.resolve(appDirectory, folder))
|
||||
.join(path.delimiter);
|
||||
|
||||
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in Webpack configuration.
|
||||
const REACT_APP = /^REACT_APP_/i;
|
||||
|
||||
function getClientEnvironment(publicUrl) {
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(key => REACT_APP.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
return env;
|
||||
},
|
||||
{
|
||||
// Useful for determining whether we’re running in production mode.
|
||||
// Most importantly, it switches React into the correct mode.
|
||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||
// Useful for resolving the correct path to static assets in `public`.
|
||||
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
|
||||
// This should only be used as an escape hatch. Normally you would put
|
||||
// images into the `src` and `import` them in code to get their paths.
|
||||
PUBLIC_URL: publicUrl,
|
||||
}
|
||||
);
|
||||
// Stringify all values so we can feed into Webpack DefinePlugin
|
||||
const stringified = {
|
||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
return env;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return { raw, stringified };
|
||||
}
|
||||
|
||||
module.exports = getClientEnvironment;
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
// This is a custom Jest transformer turning style imports into empty objects.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return 'cssTransform';
|
||||
},
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process(src, filename) {
|
||||
const assetFilename = JSON.stringify(path.basename(filename));
|
||||
|
||||
if (filename.match(/\.svg$/)) {
|
||||
return `const React = require('react');
|
||||
module.exports = {
|
||||
__esModule: true,
|
||||
default: ${assetFilename},
|
||||
ReactComponent: React.forwardRef((props, ref) => ({
|
||||
$$typeof: Symbol.for('react.element'),
|
||||
type: 'svg',
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: Object.assign({}, props, {
|
||||
children: ${assetFilename}
|
||||
})
|
||||
})),
|
||||
};`;
|
||||
}
|
||||
|
||||
return `module.exports = ${assetFilename};`;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const url = require('url');
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebook/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
|
||||
|
||||
const envPublicUrl = process.env.PUBLIC_URL;
|
||||
|
||||
function ensureSlash(inputPath, needsSlash) {
|
||||
const hasSlash = inputPath.endsWith('/');
|
||||
if (hasSlash && !needsSlash) {
|
||||
return inputPath.substr(0, inputPath.length - 1);
|
||||
} else if (!hasSlash && needsSlash) {
|
||||
return `${inputPath}/`;
|
||||
} else {
|
||||
return inputPath;
|
||||
}
|
||||
}
|
||||
|
||||
const getPublicUrl = appPackageJson =>
|
||||
envPublicUrl || require(appPackageJson).homepage;
|
||||
|
||||
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
|
||||
// "public path" at which the app is served.
|
||||
// Webpack needs to know it to put the right <script> hrefs into HTML even in
|
||||
// single-page apps that may serve index.html for nested URLs like /todos/42.
|
||||
// We can't use a relative path in HTML because we don't want to load something
|
||||
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
|
||||
function getServedPath(appPackageJson) {
|
||||
const publicUrl = getPublicUrl(appPackageJson);
|
||||
const servedUrl =
|
||||
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
|
||||
return ensureSlash(servedUrl, true);
|
||||
}
|
||||
|
||||
const moduleFileExtensions = [
|
||||
'web.mjs',
|
||||
'mjs',
|
||||
'web.js',
|
||||
'js',
|
||||
'web.ts',
|
||||
'ts',
|
||||
'web.tsx',
|
||||
'tsx',
|
||||
'json',
|
||||
'web.jsx',
|
||||
'jsx',
|
||||
];
|
||||
|
||||
// Resolve file paths in the same order as webpack
|
||||
const resolveModule = (resolveFn, filePath) => {
|
||||
const extension = moduleFileExtensions.find(extension =>
|
||||
fs.existsSync(resolveFn(`${filePath}.${extension}`))
|
||||
);
|
||||
|
||||
if (extension) {
|
||||
return resolveFn(`${filePath}.${extension}`);
|
||||
}
|
||||
|
||||
return resolveFn(`${filePath}.js`);
|
||||
};
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
module.exports = {
|
||||
dotenv: resolveApp('.env'),
|
||||
appPath: resolveApp('.'),
|
||||
appBuild: resolveApp('build'),
|
||||
appPublic: resolveApp('public'),
|
||||
appHtml: resolveApp('public/index.html'),
|
||||
appIndexJs: resolveModule(resolveApp, 'src/index'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('src'),
|
||||
appTsConfig: resolveApp('tsconfig.json'),
|
||||
yarnLockFile: resolveApp('yarn.lock'),
|
||||
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
|
||||
proxySetup: resolveApp('src/setupProxy.js'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
publicUrl: getPublicUrl(resolveApp('package.json')),
|
||||
servedPath: getServedPath(resolveApp('package.json')),
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports.moduleFileExtensions = moduleFileExtensions;
|
|
@ -0,0 +1,608 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const resolve = require('resolve');
|
||||
const PnpWebpackPlugin = require('pnp-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const safePostCssParser = require('postcss-safe-parser');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
|
||||
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||
const paths = require('./paths');
|
||||
const getClientEnvironment = require('./env');
|
||||
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
|
||||
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
|
||||
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||
console.log("using sourcemaps: " + shouldUseSourceMap);
|
||||
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
|
||||
// makes for a smoother build process.
|
||||
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
|
||||
|
||||
// Check if TypeScript is setup
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
|
||||
// style files regexes
|
||||
const cssRegex = /\.css$/;
|
||||
const cssModuleRegex = /\.module\.css$/;
|
||||
const sassRegex = /\.(scss|sass)$/;
|
||||
const sassModuleRegex = /\.module\.(scss|sass)$/;
|
||||
|
||||
// This is the production and development configuration.
|
||||
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
|
||||
module.exports = function(webpackEnv) {
|
||||
const isEnvDevelopment = webpackEnv === 'development';
|
||||
const isEnvProduction = webpackEnv === 'production';
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
const publicPath = isEnvProduction
|
||||
? paths.servedPath
|
||||
: isEnvDevelopment && '/';
|
||||
// Some apps do not use client-side routing with pushState.
|
||||
// For these, "homepage" can be set to "." to enable relative asset paths.
|
||||
const shouldUseRelativeAssetPaths = publicPath === './';
|
||||
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
|
||||
const publicUrl = isEnvProduction
|
||||
? publicPath.slice(0, -1)
|
||||
: isEnvDevelopment && '';
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// common function to get style loaders
|
||||
const getStyleLoaders = (cssOptions, preProcessor) => {
|
||||
const loaders = [
|
||||
isEnvDevelopment && require.resolve('style-loader'),
|
||||
isEnvProduction && {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: Object.assign(
|
||||
{},
|
||||
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: cssOptions,
|
||||
},
|
||||
{
|
||||
// Options for PostCSS as we reference these options twice
|
||||
// Adds vendor prefixing based on your specified browser support in
|
||||
// package.json
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebook/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
require('postcss-flexbugs-fixes'),
|
||||
require('postcss-preset-env')({
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009',
|
||||
},
|
||||
stage: 3,
|
||||
}),
|
||||
],
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
].filter(Boolean);
|
||||
if (preProcessor) {
|
||||
loaders.push({
|
||||
loader: require.resolve(preProcessor),
|
||||
options: {
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
});
|
||||
}
|
||||
return loaders;
|
||||
};
|
||||
|
||||
return {
|
||||
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
|
||||
// Stop compilation early in production
|
||||
bail: isEnvProduction,
|
||||
devtool: false,
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
entry: [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
//isEnvDevelopment &&
|
||||
// require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
// The build folder.
|
||||
path: isEnvProduction ? paths.appBuild : undefined,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: isEnvDevelopment,
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// In development, it does not produce real files.
|
||||
filename: isEnvProduction
|
||||
? 'out/[name].[contenthash:8].js'
|
||||
: isEnvDevelopment && 'out/bundle.js',
|
||||
// There are also additional JS chunk files if you use code splitting.
|
||||
chunkFilename: isEnvProduction
|
||||
? 'out/[name].[contenthash:8].chunk.js'
|
||||
: isEnvDevelopment && 'out/[name].chunk.js',
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
// We use "/" in development.
|
||||
publicPath: publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: isEnvProduction
|
||||
? info =>
|
||||
path
|
||||
.relative(paths.appSrc, info.absoluteResourcePath)
|
||||
.replace(/\\/g, '/')
|
||||
: isEnvDevelopment &&
|
||||
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
|
||||
},
|
||||
optimization: {
|
||||
minimize: isEnvProduction,
|
||||
minimizer: [
|
||||
// This is only used in production mode
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
parse: {
|
||||
// we want terser to parse ecma 8 code. However, we don't want it
|
||||
// to apply any minfication steps that turns valid ecma 5 code
|
||||
// into invalid ecma 5 code. This is why the 'compress' and 'output'
|
||||
// sections only apply transformations that are ecma 5 safe
|
||||
// https://github.com/facebook/create-react-app/pull/4234
|
||||
ecma: 8,
|
||||
},
|
||||
compress: {
|
||||
ecma: 5,
|
||||
warnings: false,
|
||||
// Disabled because of an issue with Uglify breaking seemingly valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/2376
|
||||
// Pending further investigation:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2011
|
||||
comparisons: false,
|
||||
// Disabled because of an issue with Terser breaking valid code:
|
||||
// https://github.com/facebook/create-react-app/issues/5250
|
||||
// Pending futher investigation:
|
||||
// https://github.com/terser-js/terser/issues/120
|
||||
inline: 2,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
output: {
|
||||
ecma: 5,
|
||||
comments: false,
|
||||
// Turned on because emoji and regex is not minified properly using default
|
||||
// https://github.com/facebook/create-react-app/issues/2488
|
||||
ascii_only: true,
|
||||
},
|
||||
},
|
||||
// Use multi-process parallel running to improve the build speed
|
||||
// Default number of concurrent runs: os.cpus().length - 1
|
||||
parallel: true,
|
||||
// Enable file caching
|
||||
cache: true,
|
||||
sourceMap: shouldUseSourceMap,
|
||||
}),
|
||||
// This is only used in production mode
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
parser: safePostCssParser,
|
||||
map: shouldUseSourceMap
|
||||
? {
|
||||
// `inline: false` forces the sourcemap to be output into a
|
||||
// separate file
|
||||
inline: false,
|
||||
// `annotation: true` appends the sourceMappingURL to the end of
|
||||
// the css file, helping the browser find the sourcemap
|
||||
annotation: true,
|
||||
}
|
||||
: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
// Automatically split vendor and commons
|
||||
// https://twitter.com/wSokra/status/969633336732905474
|
||||
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
name: false,
|
||||
},
|
||||
// Keep the runtime chunk separated to enable long term caching
|
||||
// https://twitter.com/wSokra/status/969679223278505985
|
||||
runtimeChunk: true,
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebook/create-react-app/issues/253
|
||||
modules: ['node_modules'].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebook/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: paths.moduleFileExtensions
|
||||
.map(ext => `.${ext}`)
|
||||
.filter(ext => useTypeScript || !ext.includes('ts')),
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web',
|
||||
},
|
||||
plugins: [
|
||||
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
|
||||
// guards against forgotten dependencies and such.
|
||||
PnpWebpackPlugin,
|
||||
// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
|
||||
],
|
||||
},
|
||||
resolveLoader: {
|
||||
plugins: [
|
||||
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
|
||||
// from the current package.
|
||||
PnpWebpackPlugin.moduleLoader(module),
|
||||
],
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// Disable require.ensure as it's not a standard language feature.
|
||||
{ parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|mjs|jsx)$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
formatter: require.resolve('react-dev-utils/eslintFormatter'),
|
||||
eslintPath: require.resolve('eslint'),
|
||||
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'out/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process application JS with Babel.
|
||||
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
|
||||
{
|
||||
test: /\.(js|mjs|jsx|ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
customize: require.resolve(
|
||||
'babel-preset-react-app/webpack-overrides'
|
||||
),
|
||||
|
||||
plugins: [
|
||||
[
|
||||
require.resolve('babel-plugin-named-asset-import'),
|
||||
{
|
||||
loaderMap: {
|
||||
svg: {
|
||||
ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
cacheCompression: isEnvProduction,
|
||||
compact: isEnvProduction,
|
||||
},
|
||||
},
|
||||
// Process any JS outside of the app with Babel.
|
||||
// Unlike the application JS, we only compile the standard ES features.
|
||||
{
|
||||
test: /\.(js|mjs)$/,
|
||||
exclude: /@babel(?:\/|\\{1,2})runtime/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
compact: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-react-app/dependencies'),
|
||||
{ helpers: true },
|
||||
],
|
||||
],
|
||||
cacheDirectory: true,
|
||||
cacheCompression: isEnvProduction,
|
||||
|
||||
// If an error happens in a package, it's possible to be
|
||||
// because it was compiled. Thus, we don't want the browser
|
||||
// debugger to show the original code. Instead, the code
|
||||
// being evaluated would be much more helpful.
|
||||
sourceMaps: false,
|
||||
},
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use MiniCSSExtractPlugin to extract that CSS
|
||||
// to a file, but in development "style" loader enables hot editing
|
||||
// of CSS.
|
||||
// By default we support CSS Modules with the extension .module.css
|
||||
{
|
||||
test: cssRegex,
|
||||
exclude: cssModuleRegex,
|
||||
use: getStyleLoaders({
|
||||
importLoaders: 1,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
}),
|
||||
// Don't consider CSS imports dead code even if the
|
||||
// containing package claims to have no side effects.
|
||||
// Remove this when webpack adds a warning or an error for this.
|
||||
// See https://github.com/webpack/webpack/issues/6571
|
||||
sideEffects: true,
|
||||
},
|
||||
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
|
||||
// using the extension .module.css
|
||||
{
|
||||
test: cssModuleRegex,
|
||||
use: getStyleLoaders({
|
||||
importLoaders: 1,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
modules: true,
|
||||
getLocalIdent: getCSSModuleLocalIdent,
|
||||
}),
|
||||
},
|
||||
// Opt-in support for SASS (using .scss or .sass extensions).
|
||||
// By default we support SASS Modules with the
|
||||
// extensions .module.scss or .module.sass
|
||||
{
|
||||
test: sassRegex,
|
||||
exclude: sassModuleRegex,
|
||||
use: getStyleLoaders(
|
||||
{
|
||||
importLoaders: 2,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
},
|
||||
'sass-loader'
|
||||
),
|
||||
// Don't consider CSS imports dead code even if the
|
||||
// containing package claims to have no side effects.
|
||||
// Remove this when webpack adds a warning or an error for this.
|
||||
// See https://github.com/webpack/webpack/issues/6571
|
||||
sideEffects: true,
|
||||
},
|
||||
// Adds support for CSS Modules, but using SASS
|
||||
// using the extension .module.scss or .module.sass
|
||||
{
|
||||
test: sassModuleRegex,
|
||||
use: getStyleLoaders(
|
||||
{
|
||||
importLoaders: 2,
|
||||
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||
modules: true,
|
||||
getLocalIdent: getCSSModuleLocalIdent,
|
||||
},
|
||||
'sass-loader'
|
||||
),
|
||||
},
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
loader: require.resolve('file-loader'),
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise be processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
options: {
|
||||
name: 'out/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
new webpack.SourceMapDevToolPlugin({
|
||||
filename: '[file].map'
|
||||
}),
|
||||
// clean the dist directory before a build
|
||||
new CleanWebpackPlugin(),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin(
|
||||
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
filename: 'index.html',
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
},
|
||||
isEnvProduction
|
||||
? {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
),
|
||||
// Inlines the webpack runtime script. This script is too small to warrant
|
||||
// a network request.
|
||||
isEnvProduction &&
|
||||
shouldInlineRuntimeChunk &&
|
||||
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In production, it will be an empty string unless you specify "homepage"
|
||||
// in `package.json`, in which case it will be the pathname of that URL.
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
|
||||
// This gives some necessary context to module not found errors, such as
|
||||
// the requesting resource.
|
||||
new ModuleNotFoundPlugin(paths.appPath),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV is set to production
|
||||
// during a production build.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebook/create-react-app/issues/240
|
||||
isEnvDevelopment && new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebook/create-react-app/issues/186
|
||||
isEnvDevelopment &&
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
isEnvProduction &&
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: 'out/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'out/css/[name].[contenthash:8].chunk.css',
|
||||
}),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
// Generate a service worker script that will precache, and keep up to date,
|
||||
// the HTML & assets that are part of the Webpack build.
|
||||
isEnvProduction &&
|
||||
new WorkboxWebpackPlugin.GenerateSW({
|
||||
clientsClaim: true,
|
||||
exclude: [/\.map$/, /asset-manifest\.json$/],
|
||||
importWorkboxFrom: 'cdn',
|
||||
navigateFallback: publicUrl + '/index.html',
|
||||
navigateFallbackBlacklist: [
|
||||
// Exclude URLs starting with /_, as they're likely an API call
|
||||
new RegExp('^/_'),
|
||||
// Exclude URLs containing a dot, as they're likely a resource in
|
||||
// public/ and not a SPA route
|
||||
new RegExp('/[^/]+\\.[^/]+$'),
|
||||
],
|
||||
}),
|
||||
// TypeScript type checking
|
||||
useTypeScript &&
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: resolve.sync('typescript', {
|
||||
basedir: paths.appNodeModules,
|
||||
}),
|
||||
async: isEnvDevelopment,
|
||||
useTypescriptIncrementalApi: true,
|
||||
checkSyntacticErrors: true,
|
||||
tsconfig: paths.appTsConfig,
|
||||
reportFiles: [
|
||||
'**',
|
||||
'!**/*.json',
|
||||
'!**/__tests__/**',
|
||||
'!**/?(*.)(spec|test).*',
|
||||
'!**/src/setupProxy.*',
|
||||
'!**/src/setupTests.*',
|
||||
],
|
||||
watch: paths.appSrc,
|
||||
silent: true,
|
||||
// The formatter is invoked directly in WebpackDevServerUtils during development
|
||||
formatter: isEnvProduction ? typescriptFormatter : undefined,
|
||||
}),
|
||||
new CopyPlugin([
|
||||
{ from: 'src', to: 'src' }
|
||||
])
|
||||
].filter(Boolean),
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
module: 'empty',
|
||||
dgram: 'empty',
|
||||
dns: 'mock',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty',
|
||||
},
|
||||
// Turn off performance processing because we utilize
|
||||
// our own hints via the FileSizeReporter
|
||||
performance: false,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,104 @@
|
|||
'use strict';
|
||||
|
||||
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
|
||||
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
|
||||
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
|
||||
const ignoredFiles = require('react-dev-utils/ignoredFiles');
|
||||
const paths = require('./paths');
|
||||
const fs = require('fs');
|
||||
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const host = process.env.HOST || '0.0.0.0';
|
||||
|
||||
module.exports = function(proxy, allowedHost) {
|
||||
return {
|
||||
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
|
||||
// websites from potentially accessing local content through DNS rebinding:
|
||||
// https://github.com/webpack/webpack-dev-server/issues/887
|
||||
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
|
||||
// However, it made several existing use cases such as development in cloud
|
||||
// environment or subdomains in development significantly more complicated:
|
||||
// https://github.com/facebook/create-react-app/issues/2271
|
||||
// https://github.com/facebook/create-react-app/issues/2233
|
||||
// While we're investigating better solutions, for now we will take a
|
||||
// compromise. Since our WDS configuration only serves files in the `public`
|
||||
// folder we won't consider accessing them a vulnerability. However, if you
|
||||
// use the `proxy` feature, it gets more dangerous because it can expose
|
||||
// remote code execution vulnerabilities in backends like Django and Rails.
|
||||
// So we will disable the host check normally, but enable it if you have
|
||||
// specified the `proxy` setting. Finally, we let you override it if you
|
||||
// really know what you're doing with a special environment variable.
|
||||
disableHostCheck:
|
||||
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
|
||||
// Enable gzip compression of generated files.
|
||||
compress: true,
|
||||
// Silence WebpackDevServer's own logs since they're generally not useful.
|
||||
// It will still show compile warnings and errors with this setting.
|
||||
clientLogLevel: 'none',
|
||||
// By default WebpackDevServer serves physical files from current directory
|
||||
// in addition to all the virtual build products that it serves from memory.
|
||||
// This is confusing because those files won’t automatically be available in
|
||||
// production build folder unless we copy them. However, copying the whole
|
||||
// project directory is dangerous because we may expose sensitive files.
|
||||
// Instead, we establish a convention that only files in `public` directory
|
||||
// get served. Our build script will copy `public` into the `build` folder.
|
||||
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
|
||||
// Note that we only recommend to use `public` folder as an escape hatch
|
||||
// for files like `favicon.ico`, `manifest.json`, and libraries that are
|
||||
// for some reason broken when imported through Webpack. If you just want to
|
||||
// use an image, put it in `src` and `import` it from JavaScript instead.
|
||||
contentBase: paths.appPublic,
|
||||
// By default files from `contentBase` will not trigger a page reload.
|
||||
watchContentBase: true,
|
||||
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
|
||||
// for the WebpackDevServer client so it can learn when the files were
|
||||
// updated. The WebpackDevServer client is included as an entry point
|
||||
// in the Webpack development configuration. Note that only changes
|
||||
// to CSS are currently hot reloaded. JS changes will refresh the browser.
|
||||
hot: true,
|
||||
// It is important to tell WebpackDevServer to use the same "root" path
|
||||
// as we specified in the config. In development, we always serve from /.
|
||||
publicPath: '/',
|
||||
// WebpackDevServer is noisy by default so we emit custom message instead
|
||||
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
|
||||
quiet: true,
|
||||
// Reportedly, this avoids CPU overload on some systems.
|
||||
// https://github.com/facebook/create-react-app/issues/293
|
||||
// src/node_modules is not ignored to support absolute imports
|
||||
// https://github.com/facebook/create-react-app/issues/1065
|
||||
watchOptions: {
|
||||
ignored: ignoredFiles(paths.appSrc),
|
||||
},
|
||||
// Enable HTTPS if the HTTPS environment variable is set to 'true'
|
||||
https: protocol === 'https',
|
||||
host,
|
||||
overlay: false,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebook/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
},
|
||||
public: allowedHost,
|
||||
proxy,
|
||||
before(app, server) {
|
||||
if (fs.existsSync(paths.proxySetup)) {
|
||||
// This registers user provided middleware for proxy reasons
|
||||
require(paths.proxySetup)(app);
|
||||
}
|
||||
|
||||
// This lets us fetch source contents from webpack for the error overlay
|
||||
app.use(evalSourceMapMiddleware(server));
|
||||
// This lets us open files from the runtime error overlay.
|
||||
app.use(errorOverlayMiddleware());
|
||||
|
||||
// This service worker file is effectively a 'no-op' that will reset any
|
||||
// previous service worker registered for the same host:port combination.
|
||||
// We do this in development to avoid hitting the production cache if
|
||||
// it used the same host and port.
|
||||
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
|
||||
app.use(noopServiceWorkerMiddleware());
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<!--
|
||||
Notice the use of in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script src="/out/bundle.js"></script><script src="/out/0.chunk.js"></script><script src="/out/main.chunk.js"></script></body>
|
||||
</html>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,860 @@
|
|||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ function webpackJsonpCallback(data) {
|
||||
/******/ var chunkIds = data[0];
|
||||
/******/ var moreModules = data[1];
|
||||
/******/ var executeModules = data[2];
|
||||
/******/
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(installedChunks[chunkId]) {
|
||||
/******/ resolves.push(installedChunks[chunkId][0]);
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||
/******/ modules[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
||||
/******/
|
||||
/******/ while(resolves.length) {
|
||||
/******/ resolves.shift()();
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // add entry modules from loaded chunk to deferred list
|
||||
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
||||
/******/
|
||||
/******/ // run deferred modules when all chunks ready
|
||||
/******/ return checkDeferredModules();
|
||||
/******/ };
|
||||
/******/ function checkDeferredModules() {
|
||||
/******/ var result;
|
||||
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
||||
/******/ var deferredModule = deferredModules[i];
|
||||
/******/ var fulfilled = true;
|
||||
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
||||
/******/ var depId = deferredModule[j];
|
||||
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
||||
/******/ }
|
||||
/******/ if(fulfilled) {
|
||||
/******/ deferredModules.splice(i--, 1);
|
||||
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ return result;
|
||||
/******/ }
|
||||
/******/ function hotDisposeChunk(chunkId) {
|
||||
/******/ delete installedChunks[chunkId];
|
||||
/******/ }
|
||||
/******/ var parentHotUpdateCallback = window["webpackHotUpdate"];
|
||||
/******/ window["webpackHotUpdate"] = // eslint-disable-next-line no-unused-vars
|
||||
/******/ function webpackHotUpdateCallback(chunkId, moreModules) {
|
||||
/******/ hotAddUpdateChunk(chunkId, moreModules);
|
||||
/******/ if (parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules);
|
||||
/******/ } ;
|
||||
/******/
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ function hotDownloadUpdateChunk(chunkId) {
|
||||
/******/ var script = document.createElement("script");
|
||||
/******/ script.charset = "utf-8";
|
||||
/******/ script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
|
||||
/******/ if (null) script.crossOrigin = null;
|
||||
/******/ document.head.appendChild(script);
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ function hotDownloadManifest(requestTimeout) {
|
||||
/******/ requestTimeout = requestTimeout || 10000;
|
||||
/******/ return new Promise(function(resolve, reject) {
|
||||
/******/ if (typeof XMLHttpRequest === "undefined") {
|
||||
/******/ return reject(new Error("No browser support"));
|
||||
/******/ }
|
||||
/******/ try {
|
||||
/******/ var request = new XMLHttpRequest();
|
||||
/******/ var requestPath = __webpack_require__.p + "" + hotCurrentHash + ".hot-update.json";
|
||||
/******/ request.open("GET", requestPath, true);
|
||||
/******/ request.timeout = requestTimeout;
|
||||
/******/ request.send(null);
|
||||
/******/ } catch (err) {
|
||||
/******/ return reject(err);
|
||||
/******/ }
|
||||
/******/ request.onreadystatechange = function() {
|
||||
/******/ if (request.readyState !== 4) return;
|
||||
/******/ if (request.status === 0) {
|
||||
/******/ // timeout
|
||||
/******/ reject(
|
||||
/******/ new Error("Manifest request to " + requestPath + " timed out.")
|
||||
/******/ );
|
||||
/******/ } else if (request.status === 404) {
|
||||
/******/ // no update available
|
||||
/******/ resolve();
|
||||
/******/ } else if (request.status !== 200 && request.status !== 304) {
|
||||
/******/ // other failure
|
||||
/******/ reject(new Error("Manifest request to " + requestPath + " failed."));
|
||||
/******/ } else {
|
||||
/******/ // success
|
||||
/******/ try {
|
||||
/******/ var update = JSON.parse(request.responseText);
|
||||
/******/ } catch (e) {
|
||||
/******/ reject(e);
|
||||
/******/ return;
|
||||
/******/ }
|
||||
/******/ resolve(update);
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ var hotApplyOnUpdate = true;
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ var hotCurrentHash = "509672cda847b6f82bc3";
|
||||
/******/ var hotRequestTimeout = 10000;
|
||||
/******/ var hotCurrentModuleData = {};
|
||||
/******/ var hotCurrentChildModule;
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ var hotCurrentParents = [];
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ var hotCurrentParentsTemp = [];
|
||||
/******/
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ function hotCreateRequire(moduleId) {
|
||||
/******/ var me = installedModules[moduleId];
|
||||
/******/ if (!me) return __webpack_require__;
|
||||
/******/ var fn = function(request) {
|
||||
/******/ if (me.hot.active) {
|
||||
/******/ if (installedModules[request]) {
|
||||
/******/ if (installedModules[request].parents.indexOf(moduleId) === -1) {
|
||||
/******/ installedModules[request].parents.push(moduleId);
|
||||
/******/ }
|
||||
/******/ } else {
|
||||
/******/ hotCurrentParents = [moduleId];
|
||||
/******/ hotCurrentChildModule = request;
|
||||
/******/ }
|
||||
/******/ if (me.children.indexOf(request) === -1) {
|
||||
/******/ me.children.push(request);
|
||||
/******/ }
|
||||
/******/ } else {
|
||||
/******/ console.warn(
|
||||
/******/ "[HMR] unexpected require(" +
|
||||
/******/ request +
|
||||
/******/ ") from disposed module " +
|
||||
/******/ moduleId
|
||||
/******/ );
|
||||
/******/ hotCurrentParents = [];
|
||||
/******/ }
|
||||
/******/ return __webpack_require__(request);
|
||||
/******/ };
|
||||
/******/ var ObjectFactory = function ObjectFactory(name) {
|
||||
/******/ return {
|
||||
/******/ configurable: true,
|
||||
/******/ enumerable: true,
|
||||
/******/ get: function() {
|
||||
/******/ return __webpack_require__[name];
|
||||
/******/ },
|
||||
/******/ set: function(value) {
|
||||
/******/ __webpack_require__[name] = value;
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/ };
|
||||
/******/ for (var name in __webpack_require__) {
|
||||
/******/ if (
|
||||
/******/ Object.prototype.hasOwnProperty.call(__webpack_require__, name) &&
|
||||
/******/ name !== "e" &&
|
||||
/******/ name !== "t"
|
||||
/******/ ) {
|
||||
/******/ Object.defineProperty(fn, name, ObjectFactory(name));
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ fn.e = function(chunkId) {
|
||||
/******/ if (hotStatus === "ready") hotSetStatus("prepare");
|
||||
/******/ hotChunksLoading++;
|
||||
/******/ return __webpack_require__.e(chunkId).then(finishChunkLoading, function(err) {
|
||||
/******/ finishChunkLoading();
|
||||
/******/ throw err;
|
||||
/******/ });
|
||||
/******/
|
||||
/******/ function finishChunkLoading() {
|
||||
/******/ hotChunksLoading--;
|
||||
/******/ if (hotStatus === "prepare") {
|
||||
/******/ if (!hotWaitingFilesMap[chunkId]) {
|
||||
/******/ hotEnsureUpdateChunk(chunkId);
|
||||
/******/ }
|
||||
/******/ if (hotChunksLoading === 0 && hotWaitingFiles === 0) {
|
||||
/******/ hotUpdateDownloaded();
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/ fn.t = function(value, mode) {
|
||||
/******/ if (mode & 1) value = fn(value);
|
||||
/******/ return __webpack_require__.t(value, mode & ~1);
|
||||
/******/ };
|
||||
/******/ return fn;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ function hotCreateModule(moduleId) {
|
||||
/******/ var hot = {
|
||||
/******/ // private stuff
|
||||
/******/ _acceptedDependencies: {},
|
||||
/******/ _declinedDependencies: {},
|
||||
/******/ _selfAccepted: false,
|
||||
/******/ _selfDeclined: false,
|
||||
/******/ _disposeHandlers: [],
|
||||
/******/ _main: hotCurrentChildModule !== moduleId,
|
||||
/******/
|
||||
/******/ // Module API
|
||||
/******/ active: true,
|
||||
/******/ accept: function(dep, callback) {
|
||||
/******/ if (dep === undefined) hot._selfAccepted = true;
|
||||
/******/ else if (typeof dep === "function") hot._selfAccepted = dep;
|
||||
/******/ else if (typeof dep === "object")
|
||||
/******/ for (var i = 0; i < dep.length; i++)
|
||||
/******/ hot._acceptedDependencies[dep[i]] = callback || function() {};
|
||||
/******/ else hot._acceptedDependencies[dep] = callback || function() {};
|
||||
/******/ },
|
||||
/******/ decline: function(dep) {
|
||||
/******/ if (dep === undefined) hot._selfDeclined = true;
|
||||
/******/ else if (typeof dep === "object")
|
||||
/******/ for (var i = 0; i < dep.length; i++)
|
||||
/******/ hot._declinedDependencies[dep[i]] = true;
|
||||
/******/ else hot._declinedDependencies[dep] = true;
|
||||
/******/ },
|
||||
/******/ dispose: function(callback) {
|
||||
/******/ hot._disposeHandlers.push(callback);
|
||||
/******/ },
|
||||
/******/ addDisposeHandler: function(callback) {
|
||||
/******/ hot._disposeHandlers.push(callback);
|
||||
/******/ },
|
||||
/******/ removeDisposeHandler: function(callback) {
|
||||
/******/ var idx = hot._disposeHandlers.indexOf(callback);
|
||||
/******/ if (idx >= 0) hot._disposeHandlers.splice(idx, 1);
|
||||
/******/ },
|
||||
/******/
|
||||
/******/ // Management API
|
||||
/******/ check: hotCheck,
|
||||
/******/ apply: hotApply,
|
||||
/******/ status: function(l) {
|
||||
/******/ if (!l) return hotStatus;
|
||||
/******/ hotStatusHandlers.push(l);
|
||||
/******/ },
|
||||
/******/ addStatusHandler: function(l) {
|
||||
/******/ hotStatusHandlers.push(l);
|
||||
/******/ },
|
||||
/******/ removeStatusHandler: function(l) {
|
||||
/******/ var idx = hotStatusHandlers.indexOf(l);
|
||||
/******/ if (idx >= 0) hotStatusHandlers.splice(idx, 1);
|
||||
/******/ },
|
||||
/******/
|
||||
/******/ //inherit from previous dispose call
|
||||
/******/ data: hotCurrentModuleData[moduleId]
|
||||
/******/ };
|
||||
/******/ hotCurrentChildModule = undefined;
|
||||
/******/ return hot;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ var hotStatusHandlers = [];
|
||||
/******/ var hotStatus = "idle";
|
||||
/******/
|
||||
/******/ function hotSetStatus(newStatus) {
|
||||
/******/ hotStatus = newStatus;
|
||||
/******/ for (var i = 0; i < hotStatusHandlers.length; i++)
|
||||
/******/ hotStatusHandlers[i].call(null, newStatus);
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // while downloading
|
||||
/******/ var hotWaitingFiles = 0;
|
||||
/******/ var hotChunksLoading = 0;
|
||||
/******/ var hotWaitingFilesMap = {};
|
||||
/******/ var hotRequestedFilesMap = {};
|
||||
/******/ var hotAvailableFilesMap = {};
|
||||
/******/ var hotDeferred;
|
||||
/******/
|
||||
/******/ // The update info
|
||||
/******/ var hotUpdate, hotUpdateNewHash;
|
||||
/******/
|
||||
/******/ function toModuleId(id) {
|
||||
/******/ var isNumber = +id + "" === id;
|
||||
/******/ return isNumber ? +id : id;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ function hotCheck(apply) {
|
||||
/******/ if (hotStatus !== "idle") {
|
||||
/******/ throw new Error("check() is only allowed in idle status");
|
||||
/******/ }
|
||||
/******/ hotApplyOnUpdate = apply;
|
||||
/******/ hotSetStatus("check");
|
||||
/******/ return hotDownloadManifest(hotRequestTimeout).then(function(update) {
|
||||
/******/ if (!update) {
|
||||
/******/ hotSetStatus("idle");
|
||||
/******/ return null;
|
||||
/******/ }
|
||||
/******/ hotRequestedFilesMap = {};
|
||||
/******/ hotWaitingFilesMap = {};
|
||||
/******/ hotAvailableFilesMap = update.c;
|
||||
/******/ hotUpdateNewHash = update.h;
|
||||
/******/
|
||||
/******/ hotSetStatus("prepare");
|
||||
/******/ var promise = new Promise(function(resolve, reject) {
|
||||
/******/ hotDeferred = {
|
||||
/******/ resolve: resolve,
|
||||
/******/ reject: reject
|
||||
/******/ };
|
||||
/******/ });
|
||||
/******/ hotUpdate = {};
|
||||
/******/ for(var chunkId in installedChunks)
|
||||
/******/ // eslint-disable-next-line no-lone-blocks
|
||||
/******/ {
|
||||
/******/ /*globals chunkId */
|
||||
/******/ hotEnsureUpdateChunk(chunkId);
|
||||
/******/ }
|
||||
/******/ if (
|
||||
/******/ hotStatus === "prepare" &&
|
||||
/******/ hotChunksLoading === 0 &&
|
||||
/******/ hotWaitingFiles === 0
|
||||
/******/ ) {
|
||||
/******/ hotUpdateDownloaded();
|
||||
/******/ }
|
||||
/******/ return promise;
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // eslint-disable-next-line no-unused-vars
|
||||
/******/ function hotAddUpdateChunk(chunkId, moreModules) {
|
||||
/******/ if (!hotAvailableFilesMap[chunkId] || !hotRequestedFilesMap[chunkId])
|
||||
/******/ return;
|
||||
/******/ hotRequestedFilesMap[chunkId] = false;
|
||||
/******/ for (var moduleId in moreModules) {
|
||||
/******/ if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
||||
/******/ hotUpdate[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if (--hotWaitingFiles === 0 && hotChunksLoading === 0) {
|
||||
/******/ hotUpdateDownloaded();
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ function hotEnsureUpdateChunk(chunkId) {
|
||||
/******/ if (!hotAvailableFilesMap[chunkId]) {
|
||||
/******/ hotWaitingFilesMap[chunkId] = true;
|
||||
/******/ } else {
|
||||
/******/ hotRequestedFilesMap[chunkId] = true;
|
||||
/******/ hotWaitingFiles++;
|
||||
/******/ hotDownloadUpdateChunk(chunkId);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ function hotUpdateDownloaded() {
|
||||
/******/ hotSetStatus("ready");
|
||||
/******/ var deferred = hotDeferred;
|
||||
/******/ hotDeferred = null;
|
||||
/******/ if (!deferred) return;
|
||||
/******/ if (hotApplyOnUpdate) {
|
||||
/******/ // Wrap deferred object in Promise to mark it as a well-handled Promise to
|
||||
/******/ // avoid triggering uncaught exception warning in Chrome.
|
||||
/******/ // See https://bugs.chromium.org/p/chromium/issues/detail?id=465666
|
||||
/******/ Promise.resolve()
|
||||
/******/ .then(function() {
|
||||
/******/ return hotApply(hotApplyOnUpdate);
|
||||
/******/ })
|
||||
/******/ .then(
|
||||
/******/ function(result) {
|
||||
/******/ deferred.resolve(result);
|
||||
/******/ },
|
||||
/******/ function(err) {
|
||||
/******/ deferred.reject(err);
|
||||
/******/ }
|
||||
/******/ );
|
||||
/******/ } else {
|
||||
/******/ var outdatedModules = [];
|
||||
/******/ for (var id in hotUpdate) {
|
||||
/******/ if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
|
||||
/******/ outdatedModules.push(toModuleId(id));
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ deferred.resolve(outdatedModules);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ function hotApply(options) {
|
||||
/******/ if (hotStatus !== "ready")
|
||||
/******/ throw new Error("apply() is only allowed in ready status");
|
||||
/******/ options = options || {};
|
||||
/******/
|
||||
/******/ var cb;
|
||||
/******/ var i;
|
||||
/******/ var j;
|
||||
/******/ var module;
|
||||
/******/ var moduleId;
|
||||
/******/
|
||||
/******/ function getAffectedStuff(updateModuleId) {
|
||||
/******/ var outdatedModules = [updateModuleId];
|
||||
/******/ var outdatedDependencies = {};
|
||||
/******/
|
||||
/******/ var queue = outdatedModules.slice().map(function(id) {
|
||||
/******/ return {
|
||||
/******/ chain: [id],
|
||||
/******/ id: id
|
||||
/******/ };
|
||||
/******/ });
|
||||
/******/ while (queue.length > 0) {
|
||||
/******/ var queueItem = queue.pop();
|
||||
/******/ var moduleId = queueItem.id;
|
||||
/******/ var chain = queueItem.chain;
|
||||
/******/ module = installedModules[moduleId];
|
||||
/******/ if (!module || module.hot._selfAccepted) continue;
|
||||
/******/ if (module.hot._selfDeclined) {
|
||||
/******/ return {
|
||||
/******/ type: "self-declined",
|
||||
/******/ chain: chain,
|
||||
/******/ moduleId: moduleId
|
||||
/******/ };
|
||||
/******/ }
|
||||
/******/ if (module.hot._main) {
|
||||
/******/ return {
|
||||
/******/ type: "unaccepted",
|
||||
/******/ chain: chain,
|
||||
/******/ moduleId: moduleId
|
||||
/******/ };
|
||||
/******/ }
|
||||
/******/ for (var i = 0; i < module.parents.length; i++) {
|
||||
/******/ var parentId = module.parents[i];
|
||||
/******/ var parent = installedModules[parentId];
|
||||
/******/ if (!parent) continue;
|
||||
/******/ if (parent.hot._declinedDependencies[moduleId]) {
|
||||
/******/ return {
|
||||
/******/ type: "declined",
|
||||
/******/ chain: chain.concat([parentId]),
|
||||
/******/ moduleId: moduleId,
|
||||
/******/ parentId: parentId
|
||||
/******/ };
|
||||
/******/ }
|
||||
/******/ if (outdatedModules.indexOf(parentId) !== -1) continue;
|
||||
/******/ if (parent.hot._acceptedDependencies[moduleId]) {
|
||||
/******/ if (!outdatedDependencies[parentId])
|
||||
/******/ outdatedDependencies[parentId] = [];
|
||||
/******/ addAllToSet(outdatedDependencies[parentId], [moduleId]);
|
||||
/******/ continue;
|
||||
/******/ }
|
||||
/******/ delete outdatedDependencies[parentId];
|
||||
/******/ outdatedModules.push(parentId);
|
||||
/******/ queue.push({
|
||||
/******/ chain: chain.concat([parentId]),
|
||||
/******/ id: parentId
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ return {
|
||||
/******/ type: "accepted",
|
||||
/******/ moduleId: updateModuleId,
|
||||
/******/ outdatedModules: outdatedModules,
|
||||
/******/ outdatedDependencies: outdatedDependencies
|
||||
/******/ };
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ function addAllToSet(a, b) {
|
||||
/******/ for (var i = 0; i < b.length; i++) {
|
||||
/******/ var item = b[i];
|
||||
/******/ if (a.indexOf(item) === -1) a.push(item);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // at begin all updates modules are outdated
|
||||
/******/ // the "outdated" status can propagate to parents if they don't accept the children
|
||||
/******/ var outdatedDependencies = {};
|
||||
/******/ var outdatedModules = [];
|
||||
/******/ var appliedUpdate = {};
|
||||
/******/
|
||||
/******/ var warnUnexpectedRequire = function warnUnexpectedRequire() {
|
||||
/******/ console.warn(
|
||||
/******/ "[HMR] unexpected require(" + result.moduleId + ") to disposed module"
|
||||
/******/ );
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ for (var id in hotUpdate) {
|
||||
/******/ if (Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
|
||||
/******/ moduleId = toModuleId(id);
|
||||
/******/ /** @type {TODO} */
|
||||
/******/ var result;
|
||||
/******/ if (hotUpdate[id]) {
|
||||
/******/ result = getAffectedStuff(moduleId);
|
||||
/******/ } else {
|
||||
/******/ result = {
|
||||
/******/ type: "disposed",
|
||||
/******/ moduleId: id
|
||||
/******/ };
|
||||
/******/ }
|
||||
/******/ /** @type {Error|false} */
|
||||
/******/ var abortError = false;
|
||||
/******/ var doApply = false;
|
||||
/******/ var doDispose = false;
|
||||
/******/ var chainInfo = "";
|
||||
/******/ if (result.chain) {
|
||||
/******/ chainInfo = "\nUpdate propagation: " + result.chain.join(" -> ");
|
||||
/******/ }
|
||||
/******/ switch (result.type) {
|
||||
/******/ case "self-declined":
|
||||
/******/ if (options.onDeclined) options.onDeclined(result);
|
||||
/******/ if (!options.ignoreDeclined)
|
||||
/******/ abortError = new Error(
|
||||
/******/ "Aborted because of self decline: " +
|
||||
/******/ result.moduleId +
|
||||
/******/ chainInfo
|
||||
/******/ );
|
||||
/******/ break;
|
||||
/******/ case "declined":
|
||||
/******/ if (options.onDeclined) options.onDeclined(result);
|
||||
/******/ if (!options.ignoreDeclined)
|
||||
/******/ abortError = new Error(
|
||||
/******/ "Aborted because of declined dependency: " +
|
||||
/******/ result.moduleId +
|
||||
/******/ " in " +
|
||||
/******/ result.parentId +
|
||||
/******/ chainInfo
|
||||
/******/ );
|
||||
/******/ break;
|
||||
/******/ case "unaccepted":
|
||||
/******/ if (options.onUnaccepted) options.onUnaccepted(result);
|
||||
/******/ if (!options.ignoreUnaccepted)
|
||||
/******/ abortError = new Error(
|
||||
/******/ "Aborted because " + moduleId + " is not accepted" + chainInfo
|
||||
/******/ );
|
||||
/******/ break;
|
||||
/******/ case "accepted":
|
||||
/******/ if (options.onAccepted) options.onAccepted(result);
|
||||
/******/ doApply = true;
|
||||
/******/ break;
|
||||
/******/ case "disposed":
|
||||
/******/ if (options.onDisposed) options.onDisposed(result);
|
||||
/******/ doDispose = true;
|
||||
/******/ break;
|
||||
/******/ default:
|
||||
/******/ throw new Error("Unexception type " + result.type);
|
||||
/******/ }
|
||||
/******/ if (abortError) {
|
||||
/******/ hotSetStatus("abort");
|
||||
/******/ return Promise.reject(abortError);
|
||||
/******/ }
|
||||
/******/ if (doApply) {
|
||||
/******/ appliedUpdate[moduleId] = hotUpdate[moduleId];
|
||||
/******/ addAllToSet(outdatedModules, result.outdatedModules);
|
||||
/******/ for (moduleId in result.outdatedDependencies) {
|
||||
/******/ if (
|
||||
/******/ Object.prototype.hasOwnProperty.call(
|
||||
/******/ result.outdatedDependencies,
|
||||
/******/ moduleId
|
||||
/******/ )
|
||||
/******/ ) {
|
||||
/******/ if (!outdatedDependencies[moduleId])
|
||||
/******/ outdatedDependencies[moduleId] = [];
|
||||
/******/ addAllToSet(
|
||||
/******/ outdatedDependencies[moduleId],
|
||||
/******/ result.outdatedDependencies[moduleId]
|
||||
/******/ );
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if (doDispose) {
|
||||
/******/ addAllToSet(outdatedModules, [result.moduleId]);
|
||||
/******/ appliedUpdate[moduleId] = warnUnexpectedRequire;
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // Store self accepted outdated modules to require them later by the module system
|
||||
/******/ var outdatedSelfAcceptedModules = [];
|
||||
/******/ for (i = 0; i < outdatedModules.length; i++) {
|
||||
/******/ moduleId = outdatedModules[i];
|
||||
/******/ if (
|
||||
/******/ installedModules[moduleId] &&
|
||||
/******/ installedModules[moduleId].hot._selfAccepted
|
||||
/******/ )
|
||||
/******/ outdatedSelfAcceptedModules.push({
|
||||
/******/ module: moduleId,
|
||||
/******/ errorHandler: installedModules[moduleId].hot._selfAccepted
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // Now in "dispose" phase
|
||||
/******/ hotSetStatus("dispose");
|
||||
/******/ Object.keys(hotAvailableFilesMap).forEach(function(chunkId) {
|
||||
/******/ if (hotAvailableFilesMap[chunkId] === false) {
|
||||
/******/ hotDisposeChunk(chunkId);
|
||||
/******/ }
|
||||
/******/ });
|
||||
/******/
|
||||
/******/ var idx;
|
||||
/******/ var queue = outdatedModules.slice();
|
||||
/******/ while (queue.length > 0) {
|
||||
/******/ moduleId = queue.pop();
|
||||
/******/ module = installedModules[moduleId];
|
||||
/******/ if (!module) continue;
|
||||
/******/
|
||||
/******/ var data = {};
|
||||
/******/
|
||||
/******/ // Call dispose handlers
|
||||
/******/ var disposeHandlers = module.hot._disposeHandlers;
|
||||
/******/ for (j = 0; j < disposeHandlers.length; j++) {
|
||||
/******/ cb = disposeHandlers[j];
|
||||
/******/ cb(data);
|
||||
/******/ }
|
||||
/******/ hotCurrentModuleData[moduleId] = data;
|
||||
/******/
|
||||
/******/ // disable module (this disables requires from this module)
|
||||
/******/ module.hot.active = false;
|
||||
/******/
|
||||
/******/ // remove module from cache
|
||||
/******/ delete installedModules[moduleId];
|
||||
/******/
|
||||
/******/ // when disposing there is no need to call dispose handler
|
||||
/******/ delete outdatedDependencies[moduleId];
|
||||
/******/
|
||||
/******/ // remove "parents" references from all children
|
||||
/******/ for (j = 0; j < module.children.length; j++) {
|
||||
/******/ var child = installedModules[module.children[j]];
|
||||
/******/ if (!child) continue;
|
||||
/******/ idx = child.parents.indexOf(moduleId);
|
||||
/******/ if (idx >= 0) {
|
||||
/******/ child.parents.splice(idx, 1);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // remove outdated dependency from module children
|
||||
/******/ var dependency;
|
||||
/******/ var moduleOutdatedDependencies;
|
||||
/******/ for (moduleId in outdatedDependencies) {
|
||||
/******/ if (
|
||||
/******/ Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)
|
||||
/******/ ) {
|
||||
/******/ module = installedModules[moduleId];
|
||||
/******/ if (module) {
|
||||
/******/ moduleOutdatedDependencies = outdatedDependencies[moduleId];
|
||||
/******/ for (j = 0; j < moduleOutdatedDependencies.length; j++) {
|
||||
/******/ dependency = moduleOutdatedDependencies[j];
|
||||
/******/ idx = module.children.indexOf(dependency);
|
||||
/******/ if (idx >= 0) module.children.splice(idx, 1);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // Not in "apply" phase
|
||||
/******/ hotSetStatus("apply");
|
||||
/******/
|
||||
/******/ hotCurrentHash = hotUpdateNewHash;
|
||||
/******/
|
||||
/******/ // insert new code
|
||||
/******/ for (moduleId in appliedUpdate) {
|
||||
/******/ if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
|
||||
/******/ modules[moduleId] = appliedUpdate[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // call accept handlers
|
||||
/******/ var error = null;
|
||||
/******/ for (moduleId in outdatedDependencies) {
|
||||
/******/ if (
|
||||
/******/ Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)
|
||||
/******/ ) {
|
||||
/******/ module = installedModules[moduleId];
|
||||
/******/ if (module) {
|
||||
/******/ moduleOutdatedDependencies = outdatedDependencies[moduleId];
|
||||
/******/ var callbacks = [];
|
||||
/******/ for (i = 0; i < moduleOutdatedDependencies.length; i++) {
|
||||
/******/ dependency = moduleOutdatedDependencies[i];
|
||||
/******/ cb = module.hot._acceptedDependencies[dependency];
|
||||
/******/ if (cb) {
|
||||
/******/ if (callbacks.indexOf(cb) !== -1) continue;
|
||||
/******/ callbacks.push(cb);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ for (i = 0; i < callbacks.length; i++) {
|
||||
/******/ cb = callbacks[i];
|
||||
/******/ try {
|
||||
/******/ cb(moduleOutdatedDependencies);
|
||||
/******/ } catch (err) {
|
||||
/******/ if (options.onErrored) {
|
||||
/******/ options.onErrored({
|
||||
/******/ type: "accept-errored",
|
||||
/******/ moduleId: moduleId,
|
||||
/******/ dependencyId: moduleOutdatedDependencies[i],
|
||||
/******/ error: err
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ if (!options.ignoreErrored) {
|
||||
/******/ if (!error) error = err;
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // Load self accepted modules
|
||||
/******/ for (i = 0; i < outdatedSelfAcceptedModules.length; i++) {
|
||||
/******/ var item = outdatedSelfAcceptedModules[i];
|
||||
/******/ moduleId = item.module;
|
||||
/******/ hotCurrentParents = [moduleId];
|
||||
/******/ try {
|
||||
/******/ __webpack_require__(moduleId);
|
||||
/******/ } catch (err) {
|
||||
/******/ if (typeof item.errorHandler === "function") {
|
||||
/******/ try {
|
||||
/******/ item.errorHandler(err);
|
||||
/******/ } catch (err2) {
|
||||
/******/ if (options.onErrored) {
|
||||
/******/ options.onErrored({
|
||||
/******/ type: "self-accept-error-handler-errored",
|
||||
/******/ moduleId: moduleId,
|
||||
/******/ error: err2,
|
||||
/******/ originalError: err
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ if (!options.ignoreErrored) {
|
||||
/******/ if (!error) error = err2;
|
||||
/******/ }
|
||||
/******/ if (!error) error = err;
|
||||
/******/ }
|
||||
/******/ } else {
|
||||
/******/ if (options.onErrored) {
|
||||
/******/ options.onErrored({
|
||||
/******/ type: "self-accept-errored",
|
||||
/******/ moduleId: moduleId,
|
||||
/******/ error: err
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ if (!options.ignoreErrored) {
|
||||
/******/ if (!error) error = err;
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // handle errors in accept handlers and self accepted module load
|
||||
/******/ if (error) {
|
||||
/******/ hotSetStatus("fail");
|
||||
/******/ return Promise.reject(error);
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ hotSetStatus("idle");
|
||||
/******/ return new Promise(function(resolve) {
|
||||
/******/ resolve(outdatedModules);
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // Promise = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "runtime~main": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ var deferredModules = [];
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {},
|
||||
/******/ hot: hotCreateModule(moduleId),
|
||||
/******/ parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
|
||||
/******/ children: []
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "/";
|
||||
/******/
|
||||
/******/ // __webpack_hash__
|
||||
/******/ __webpack_require__.h = function() { return hotCurrentHash; };
|
||||
/******/
|
||||
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
||||
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
||||
/******/ jsonpArray.push = webpackJsonpCallback;
|
||||
/******/ jsonpArray = jsonpArray.slice();
|
||||
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
||||
/******/ var parentJsonpFunction = oldJsonpFunction;
|
||||
/******/
|
||||
/******/
|
||||
/******/ // run deferred modules from other chunks
|
||||
/******/ checkDeferredModules();
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([]);
|
||||
//# sourceMappingURL=bundle.js.map
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,190 @@
|
|||
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["main"],{
|
||||
|
||||
/***/ "./src/App.js":
|
||||
/*!********************!*\
|
||||
!*** ./src/App.js ***!
|
||||
\********************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/classCallCheck */ "./node_modules/@babel/runtime/helpers/esm/classCallCheck.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/createClass */ "./node_modules/@babel/runtime/helpers/esm/createClass.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn */ "./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/getPrototypeOf */ "./node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/inherits */ "./node_modules/@babel/runtime/helpers/esm/inherits.js");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
|
||||
/* harmony import */ var _Counter__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./Counter */ "./src/Counter.jsx");
|
||||
/* harmony import */ var _Running__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./Running */ "./src/Running.js");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var App =
|
||||
/*#__PURE__*/
|
||||
function (_Component) {
|
||||
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__["default"])(App, _Component);
|
||||
|
||||
function App() {
|
||||
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, App);
|
||||
|
||||
return Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__["default"])(this, Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__["default"])(App).apply(this, arguments));
|
||||
}
|
||||
|
||||
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__["default"])(App, [{
|
||||
key: "render",
|
||||
value: function render() {
|
||||
return react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement(_Counter__WEBPACK_IMPORTED_MODULE_6__["Counter"], null);
|
||||
}
|
||||
}]);
|
||||
|
||||
return App;
|
||||
}(react__WEBPACK_IMPORTED_MODULE_5__["Component"]);
|
||||
|
||||
Object(_Running__WEBPACK_IMPORTED_MODULE_7__["runForever"])();
|
||||
/* harmony default export */ __webpack_exports__["default"] = (App);
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/Counter.jsx":
|
||||
/*!*************************!*\
|
||||
!*** ./src/Counter.jsx ***!
|
||||
\*************************/
|
||||
/*! exports provided: Counter */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Counter", function() { return Counter; });
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/classCallCheck */ "./node_modules/@babel/runtime/helpers/esm/classCallCheck.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/createClass */ "./node_modules/@babel/runtime/helpers/esm/createClass.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn */ "./node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/getPrototypeOf */ "./node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js");
|
||||
/* harmony import */ var C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/inherits */ "./node_modules/@babel/runtime/helpers/esm/inherits.js");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var Counter =
|
||||
/*#__PURE__*/
|
||||
function (_Component) {
|
||||
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_inherits__WEBPACK_IMPORTED_MODULE_4__["default"])(Counter, _Component);
|
||||
|
||||
function Counter(props) {
|
||||
var _this;
|
||||
|
||||
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, Counter);
|
||||
|
||||
_this = Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_possibleConstructorReturn__WEBPACK_IMPORTED_MODULE_2__["default"])(this, Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_getPrototypeOf__WEBPACK_IMPORTED_MODULE_3__["default"])(Counter).call(this, props));
|
||||
_this.state = {
|
||||
count: 0
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
|
||||
Object(C_ws_vscode_chrome_debug_testdata_react_with_loop_node_modules_babel_runtime_helpers_esm_createClass__WEBPACK_IMPORTED_MODULE_1__["default"])(Counter, [{
|
||||
key: "increment",
|
||||
value: function increment() {
|
||||
var newval = this.state.count + 1;
|
||||
this.setState({
|
||||
count: newval
|
||||
});
|
||||
this.loop();
|
||||
}
|
||||
}, {
|
||||
key: "render",
|
||||
value: function render() {
|
||||
var _this2 = this;
|
||||
|
||||
return react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement("div", {
|
||||
className: "shopping-list"
|
||||
}, "Click count = ", this.state.count, ";", react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement("button", {
|
||||
id: "incrementBtn",
|
||||
onClick: function onClick() {
|
||||
return _this2.increment();
|
||||
}
|
||||
}, "Increment"), " "));
|
||||
}
|
||||
}, {
|
||||
key: "loop",
|
||||
value: function loop() {
|
||||
for (var iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {
|
||||
console.log("starting iteration: ".concat(iterationNumber));
|
||||
var squared = iterationNumber * iterationNumber;
|
||||
console.log("ending iteration: ".concat(iterationNumber, " squared: ").concat(squared));
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
return Counter;
|
||||
}(react__WEBPACK_IMPORTED_MODULE_5__["Component"]);
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/Running.js":
|
||||
/*!************************!*\
|
||||
!*** ./src/Running.js ***!
|
||||
\************************/
|
||||
/*! exports provided: runForever */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runForever", function() { return runForever; });
|
||||
// a script to keep running forever
|
||||
var num = 0;
|
||||
function runForever() {
|
||||
setTimeout(function () {
|
||||
num++;
|
||||
runForever();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/index.js":
|
||||
/*!**********************!*\
|
||||
!*** ./src/index.js ***!
|
||||
\**********************/
|
||||
/*! no exports provided */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");
|
||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
|
||||
/* harmony import */ var _App__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./App */ "./src/App.js");
|
||||
|
||||
|
||||
|
||||
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_App__WEBPACK_IMPORTED_MODULE_2__["default"], null), document.getElementById('root'));
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 0:
|
||||
/*!****************************!*\
|
||||
!*** multi ./src/index.js ***!
|
||||
\****************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
module.exports = __webpack_require__(/*! C:\ws\vscode-chrome-debug\testdata\react_with_loop\src\index.js */"./src/index.js");
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
},[[0,"runtime~main",0]]]);
|
||||
//# sourceMappingURL=main.chunk.js.map
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///./src/App.js","webpack:///./src/Counter.jsx","webpack:///./src/Running.js","webpack:///./src/index.js"],"names":["App","Component","runForever","Counter","props","state","count","newval","setState","loop","increment","iterationNumber","console","log","squared","num","setTimeout","ReactDOM","render","document","getElementById"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;;IAEMA,G;;;;;;;;;;;;;6BACK;AACP,aACE,2DAAC,gDAAD,OADF;AAGD;;;;EALeC,+C;;AAQlBC,2DAAU;AAEKF,kEAAf,E;;;;;;;;;;;;;;;;;;;;;;;;;;ACbA;AAEO,IAAMG,OAAb;AAAA;AAAA;AAAA;;AAEE,mBAAYC,KAAZ,EAAmB;AAAA;;AAAA;;AACjB,iWAAMA,KAAN;AAEA,UAAKC,KAAL,GAAa;AACXC,WAAK,EAAE;AADI,KAAb;AAHiB;AAMlB;;AARH;AAAA;AAAA,gCAUgB;AACV,UAAMC,MAAM,GAAG,KAAKF,KAAL,CAAWC,KAAX,GAAmB,CAAlC;AACA,WAAKE,QAAL,CAAc;AAAEF,aAAK,EAAEC;AAAT,OAAd;AACA,WAAKE,IAAL;AACD;AAdL;AAAA;AAAA,6BAgBa;AAAA;;AACP,aACE;AAAK,iBAAS,EAAC;AAAf,2BACiB,KAAKJ,KAAL,CAAWC,KAD5B,OAEE,wEACE;AAAQ,UAAE,EAAC,cAAX;AAA0B,eAAO,EAAG;AAAA,iBAAM,MAAI,CAACI,SAAL,EAAN;AAAA;AAApC,qBADF,MAFF,CADF;AAQD;AAzBL;AAAA;AAAA,2BA2BW;AACL,WAAK,IAAIC,eAAe,GAAG,CAA3B,EAA8BA,eAAe,GAAG,GAAhD,EAAqD,EAAEA,eAAvD,EAAwE;AACtEC,eAAO,CAACC,GAAR,+BAAmCF,eAAnC;AACA,YAAMG,OAAO,GAAGH,eAAe,GAAGA,eAAlC;AACAC,eAAO,CAACC,GAAR,6BAAiCF,eAAjC,uBAA6DG,OAA7D;AACD;AACF;AAjCL;;AAAA;AAAA,EAA6Bb,+CAA7B,E;;;;;;;;;;;;ACFA;AAAA;AAAA;AAEA,IAAIc,GAAG,GAAG,CAAV;AACO,SAASb,UAAT,GAAsB;AACzBc,YAAU,CAAC,YAAM;AACbD,OAAG;AACHb,cAAU;AACb,GAHS,EAGP,EAHO,CAAV;AAIH,C;;;;;;;;;;;;ACTD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AAEAe,gDAAQ,CAACC,MAAT,CAAgB,2DAAC,4CAAD,OAAhB,EAAyBC,QAAQ,CAACC,cAAT,CAAwB,MAAxB,CAAzB,E","file":"out/main.chunk.js","sourcesContent":["import React, { Component } from 'react';\r\nimport { Counter } from './Counter';\r\nimport { runForever } from './Running';\r\n\r\nclass App extends Component {\r\n render() {\r\n return (\r\n <Counter ></Counter>\r\n );\r\n }\r\n}\r\n\r\nrunForever();\r\n\r\nexport default App;\r\n","\r\nimport React, { Component } from 'react';\r\n\r\nexport class Counter extends Component {\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.state = {\r\n count: 0\r\n };\r\n }\r\n\r\n increment() {\r\n const newval = this.state.count + 1 ;\r\n this.setState({ count: newval });\r\n this.loop();\r\n }\r\n\r\n render() {\r\n return (\r\n <div className=\"shopping-list\">\r\n Click count = {this.state.count};\r\n <div>\r\n <button id=\"incrementBtn\" onClick={ () => this.increment() } >Increment</button> { }\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n loop() {\r\n for (let iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {\r\n console.log(`starting iteration: ${iterationNumber}`);\r\n const squared = iterationNumber * iterationNumber;\r\n console.log(`ending iteration: ${iterationNumber} squared: ${squared}`);\r\n }\r\n }\r\n }\r\n\r\n","\r\n// a script to keep running forever\r\n\r\nlet num = 0;\r\nexport function runForever() {\r\n setTimeout(() => {\r\n num++;\r\n runForever();\r\n }, 50);\r\n}","import React from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport App from './App';\r\n\r\nReactDOM.render(<App />, document.getElementById('root'));\r\n"],"sourceRoot":""}
|
|
@ -0,0 +1,15 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Counter } from './Counter';
|
||||
import { runForever } from './Running';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Counter ></Counter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
runForever();
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Counter extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
|
||||
increment() {
|
||||
const newval = this.state.count + 1 ;
|
||||
this.setState({ count: newval });
|
||||
this.loop();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="shopping-list">
|
||||
Click count = {this.state.count};
|
||||
<div>
|
||||
<button id="incrementBtn" onClick={ () => this.increment() } >Increment</button> { }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
loop() {
|
||||
for (let iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {
|
||||
console.log(`starting iteration: ${iterationNumber}`);
|
||||
const squared = iterationNumber * iterationNumber;
|
||||
console.log(`ending iteration: ${iterationNumber} squared: ${squared}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
// a script to keep running forever
|
||||
|
||||
let num = 0;
|
||||
export function runForever() {
|
||||
setTimeout(() => {
|
||||
num++;
|
||||
runForever();
|
||||
}, 50);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"name": "my-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.2.2",
|
||||
"@svgr/webpack": "4.1.0",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "9.0.0",
|
||||
"babel-jest": "23.6.0",
|
||||
"babel-loader": "8.0.5",
|
||||
"babel-plugin-named-asset-import": "^0.3.1",
|
||||
"babel-preset-react-app": "^7.0.2",
|
||||
"bfj": "6.1.1",
|
||||
"case-sensitive-paths-webpack-plugin": "2.2.0",
|
||||
"css-loader": "1.0.0",
|
||||
"dotenv": "6.0.0",
|
||||
"dotenv-expand": "4.2.0",
|
||||
"eslint": "5.12.0",
|
||||
"eslint-config-react-app": "^3.0.8",
|
||||
"eslint-loader": "2.1.1",
|
||||
"eslint-plugin-flowtype": "2.50.1",
|
||||
"eslint-plugin-import": "2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "6.1.2",
|
||||
"eslint-plugin-react": "7.12.4",
|
||||
"file-loader": "2.0.0",
|
||||
"fs-extra": "7.0.1",
|
||||
"html-webpack-plugin": "4.0.0-alpha.2",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"jest": "23.6.0",
|
||||
"jest-pnp-resolver": "1.0.2",
|
||||
"jest-resolve": "23.6.0",
|
||||
"jest-watch-typeahead": "^0.2.1",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||
"pnp-webpack-plugin": "1.2.1",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-preset-env": "6.5.0",
|
||||
"postcss-safe-parser": "4.0.1",
|
||||
"react": "^16.8.4",
|
||||
"react-app-polyfill": "^0.2.2",
|
||||
"react-dev-utils": "^8.0.0",
|
||||
"react-dom": "^16.8.4",
|
||||
"resolve": "1.10.0",
|
||||
"sass-loader": "7.1.0",
|
||||
"style-loader": "0.23.1",
|
||||
"terser-webpack-plugin": "1.2.2",
|
||||
"url-loader": "1.1.2",
|
||||
"webpack": "4.28.3",
|
||||
"webpack-dev-server": "3.1.14",
|
||||
"webpack-manifest-plugin": "2.0.4",
|
||||
"workbox-webpack-plugin": "3.6.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "node scripts/test.js"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
],
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx,ts,tsx}",
|
||||
"!src/**/*.d.ts"
|
||||
],
|
||||
"resolver": "jest-pnp-resolver",
|
||||
"setupFiles": [
|
||||
"react-app-polyfill/jsdom"
|
||||
],
|
||||
"testMatch": [
|
||||
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
|
||||
"<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}"
|
||||
],
|
||||
"testEnvironment": "jsdom",
|
||||
"testURL": "http://localhost",
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
|
||||
"^.+\\.module\\.(css|sass|scss)$"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^react-native$": "react-native-web",
|
||||
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"web.js",
|
||||
"js",
|
||||
"web.ts",
|
||||
"ts",
|
||||
"web.tsx",
|
||||
"tsx",
|
||||
"json",
|
||||
"web.jsx",
|
||||
"jsx",
|
||||
"node"
|
||||
],
|
||||
"watchPlugins": [
|
||||
"C:\\development\\react_test_project\\my-app\\node_modules\\jest-watch-typeahead\\filename.js",
|
||||
"C:\\development\\react_test_project\\my-app\\node_modules\\jest-watch-typeahead\\testname.js"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-webpack-plugin": "^2.0.0",
|
||||
"copy-webpack-plugin": "^5.0.1"
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 3.8 KiB |
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'production';
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const path = require('path');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const fs = require('fs-extra');
|
||||
const webpack = require('webpack');
|
||||
const bfj = require('bfj');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const paths = require('../config/paths');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
|
||||
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
|
||||
const printBuildError = require('react-dev-utils/printBuildError');
|
||||
|
||||
const measureFileSizesBeforeBuild =
|
||||
FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
|
||||
// These sizes are pretty large. We'll warn for bundles exceeding them.
|
||||
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
|
||||
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
||||
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Process CLI arguments
|
||||
const argv = process.argv.slice(2);
|
||||
const writeStatsJson = argv.indexOf('--stats') !== -1;
|
||||
|
||||
// Generate configuration
|
||||
const config = configFactory('development');
|
||||
|
||||
// We require that you explicitly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
return measureFileSizesBeforeBuild(paths.appBuild);
|
||||
})
|
||||
.then(previousFileSizes => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appBuild);
|
||||
// Merge with the public folder
|
||||
copyPublicFolder();
|
||||
// Start the webpack build
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({ stats, previousFileSizes, warnings }) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.\n'));
|
||||
console.log(warnings.join('\n\n'));
|
||||
console.log(
|
||||
'\nSearch for the ' +
|
||||
chalk.underline(chalk.yellow('keywords')) +
|
||||
' to learn more about each warning.'
|
||||
);
|
||||
console.log(
|
||||
'To ignore, add ' +
|
||||
chalk.cyan('// eslint-disable-next-line') +
|
||||
' to the line before.\n'
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green('Compiled successfully.\n'));
|
||||
}
|
||||
|
||||
// console.log('File sizes after gzip:\n');
|
||||
// printFileSizesAfterBuild(
|
||||
// stats,
|
||||
// previousFileSizes,
|
||||
// paths.appBuild,
|
||||
// WARN_AFTER_BUNDLE_GZIP_SIZE,
|
||||
// WARN_AFTER_CHUNK_GZIP_SIZE
|
||||
// );
|
||||
// console.log();
|
||||
|
||||
const appPackage = require(paths.appPackageJson);
|
||||
const publicUrl = paths.publicUrl;
|
||||
const publicPath = config.output.publicPath;
|
||||
const buildFolder = path.relative(process.cwd(), paths.appBuild);
|
||||
printHostingInstructions(
|
||||
appPackage,
|
||||
publicUrl,
|
||||
publicPath,
|
||||
buildFolder,
|
||||
useYarn
|
||||
);
|
||||
},
|
||||
err => {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
process.exit(1);
|
||||
}
|
||||
)
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
|
||||
console.log('Creating a nice debug build...');
|
||||
|
||||
let compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
let messages;
|
||||
if (err) {
|
||||
if (!err.message) {
|
||||
return reject(err);
|
||||
}
|
||||
messages = formatWebpackMessages({
|
||||
errors: [err.message],
|
||||
warnings: [],
|
||||
});
|
||||
} else {
|
||||
messages = formatWebpackMessages(
|
||||
stats.toJson({ all: false, warnings: true, errors: true })
|
||||
);
|
||||
}
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
if (messages.errors.length > 1) {
|
||||
messages.errors.length = 1;
|
||||
}
|
||||
return reject(new Error(messages.errors.join('\n\n')));
|
||||
}
|
||||
if (
|
||||
process.env.CI &&
|
||||
(typeof process.env.CI !== 'string' ||
|
||||
process.env.CI.toLowerCase() !== 'false') &&
|
||||
messages.warnings.length
|
||||
) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nTreating warnings as errors because process.env.CI = true.\n' +
|
||||
'Most CI servers set it automatically.\n'
|
||||
)
|
||||
);
|
||||
return reject(new Error(messages.warnings.join('\n\n')));
|
||||
}
|
||||
|
||||
const resolveArgs = {
|
||||
stats,
|
||||
previousFileSizes,
|
||||
warnings: messages.warnings,
|
||||
};
|
||||
if (writeStatsJson) {
|
||||
return bfj
|
||||
.write(paths.appBuild + '/bundle-stats.json', stats.toJson())
|
||||
.then(() => resolve(resolveArgs))
|
||||
.catch(error => reject(new Error(error)));
|
||||
}
|
||||
|
||||
return resolve(resolveArgs);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const fs = require('fs');
|
||||
const chalk = require('react-dev-utils/chalk');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const clearConsole = require('react-dev-utils/clearConsole');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareProxy,
|
||||
prepareUrls,
|
||||
} = require('react-dev-utils/WebpackDevServerUtils');
|
||||
const openBrowser = require('react-dev-utils/openBrowser');
|
||||
const paths = require('../config/paths');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const createDevServerConfig = require('../config/webpackDevServer.config');
|
||||
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Tools like Cloud9 rely on this.
|
||||
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
|
||||
if (process.env.HOST) {
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
`Attempting to bind to HOST environment variable: ${chalk.yellow(
|
||||
chalk.bold(process.env.HOST)
|
||||
)}`
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
|
||||
);
|
||||
console.log(
|
||||
`Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}`
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// We require that you explictly set browsers and do not fall back to
|
||||
// browserslist defaults.
|
||||
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
|
||||
checkBrowsers(paths.appPath, isInteractive)
|
||||
.then(() => {
|
||||
// We attempt to use the default port but if it is busy, we offer the user to
|
||||
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
||||
return choosePort(HOST, DEFAULT_PORT);
|
||||
})
|
||||
.then(port => {
|
||||
if (port == null) {
|
||||
// We have not found a port.
|
||||
return;
|
||||
}
|
||||
const config = configFactory('development');
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||
const urls = prepareUrls(protocol, HOST, port);
|
||||
const devSocket = {
|
||||
warnings: warnings =>
|
||||
devServer.sockWrite(devServer.sockets, 'warnings', warnings),
|
||||
errors: errors =>
|
||||
devServer.sockWrite(devServer.sockets, 'errors', errors),
|
||||
};
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = createCompiler({
|
||||
appName,
|
||||
config,
|
||||
devSocket,
|
||||
urls,
|
||||
useYarn,
|
||||
useTypeScript,
|
||||
webpack,
|
||||
});
|
||||
// Load proxy config
|
||||
const proxySetting = require(paths.appPackageJson).proxy;
|
||||
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
||||
// Serve webpack assets generated by the compiler over a web server.
|
||||
const serverConfig = createDevServerConfig(
|
||||
proxyConfig,
|
||||
urls.lanUrlForConfig
|
||||
);
|
||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, HOST, err => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
}
|
||||
console.log(chalk.cyan('Starting the development server...\n'));
|
||||
openBrowser(urls.localUrlForBrowser);
|
||||
});
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach(function(sig) {
|
||||
process.on(sig, function() {
|
||||
devServer.close();
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'test';
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PUBLIC_URL = '';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
|
||||
const jest = require('jest');
|
||||
const execSync = require('child_process').execSync;
|
||||
let argv = process.argv.slice(2);
|
||||
|
||||
function isInGitRepository() {
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInMercurialRepository() {
|
||||
try {
|
||||
execSync('hg --cwd . root', { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch unless on CI, in coverage mode, explicitly adding `--no-watch`,
|
||||
// or explicitly running all tests
|
||||
if (
|
||||
!process.env.CI &&
|
||||
argv.indexOf('--coverage') === -1 &&
|
||||
argv.indexOf('--no-watch') === -1 &&
|
||||
argv.indexOf('--watchAll') === -1
|
||||
) {
|
||||
// https://github.com/facebook/create-react-app/issues/5210
|
||||
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
|
||||
argv.push(hasSourceControl ? '--watch' : '--watchAll');
|
||||
}
|
||||
|
||||
// Jest doesn't have this option so we'll remove it
|
||||
if (argv.indexOf('--no-watch') !== -1) {
|
||||
argv = argv.filter(arg => arg !== '--no-watch');
|
||||
}
|
||||
|
||||
|
||||
jest.run(argv);
|
|
@ -0,0 +1,15 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Counter } from './Counter';
|
||||
import { runForever } from './Running';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Counter ></Counter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
runForever();
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Counter extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
|
||||
increment() {
|
||||
const newval = this.state.count + 1 ;
|
||||
this.setState({ count: newval });
|
||||
this.loop();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="shopping-list">
|
||||
Click count = {this.state.count};
|
||||
<div>
|
||||
<button id="incrementBtn" onClick={ () => this.increment() } >Increment</button> { }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
loop() {
|
||||
for (let iterationNumber = 1; iterationNumber < 200; ++iterationNumber) {
|
||||
console.log(`starting iteration: ${iterationNumber}`);
|
||||
const squared = iterationNumber * iterationNumber;
|
||||
console.log(`ending iteration: ${iterationNumber} squared: ${squared}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
// a script to keep running forever
|
||||
|
||||
let num = 0;
|
||||
export function runForever() {
|
||||
setTimeout(() => {
|
||||
num++;
|
||||
runForever();
|
||||
}, 50);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
|
@ -1,9 +1,13 @@
|
|||
function buttonClick() {
|
||||
setTimeout(inner, 100);
|
||||
setTimeout(timeoutCallback, 100);
|
||||
}
|
||||
|
||||
function inner() {
|
||||
function timeoutCallback() {
|
||||
eval("evalCallback();");
|
||||
}
|
||||
|
||||
function evalCallback() {
|
||||
(function() {
|
||||
console.log('Inside anonymous function'); // bpLabel: stackTraceBreakpoint
|
||||
console.log('Test stack trace here');
|
||||
})();
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
// "debugServer": 4712
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
(function () {
|
||||
console.log('TestCode: START');
|
||||
let i;
|
||||
for (i = 7; i < 10; ++i) {
|
||||
console.log('TestCode: BEFORE-ADDING-VARIABLES');
|
||||
|
||||
// Try to create a variable of each important type, to verify that we can see their contents properly
|
||||
|
||||
const globalCode = 'page loaded';
|
||||
|
||||
function consoleDotLog(m) {
|
||||
console.log(m)
|
||||
}
|
||||
|
||||
const manyPropsObj = { prop2: 'abc', prop1: 'def' };
|
||||
for (let i = 0; i <= 100; i++) manyPropsObj[i] = 2 * i + 1;
|
||||
|
||||
let r = /^asdf.*$/g;
|
||||
let longStr = `this is a
|
||||
string with
|
||||
newlines`;
|
||||
let element = document.body;
|
||||
const buffer = new ArrayBuffer(8);
|
||||
let buffView = new Int32Array(buffer);
|
||||
buffView[0] = 234;
|
||||
let s = Symbol('hi');
|
||||
let e = new Error('hi');
|
||||
|
||||
let m = new Map();
|
||||
m.set('a', 1);
|
||||
|
||||
let b = document.body;
|
||||
const nan = NaN;
|
||||
let inf = 1 / 0;
|
||||
let infStr = "Infinity";
|
||||
|
||||
// These 3 are going to be global variables
|
||||
eval('let evalVar3 = [1,2,3]');
|
||||
eval('let evalVar1 = 16');
|
||||
eval('let evalVar2 = "sdlfk"');
|
||||
|
||||
let bool = true;
|
||||
const fn = () => {
|
||||
// Some fn
|
||||
let xyz = 321;
|
||||
anotherFn();
|
||||
};
|
||||
let fn2 = function () {
|
||||
let zzz = 333;
|
||||
};
|
||||
let qqq;
|
||||
let str = 'hello';
|
||||
let xyz = 1;
|
||||
let obj = { a: 2, get thing() { throw 'xyz'; }, set thing(x) { } };
|
||||
xyz++; xyz++; xyz++;
|
||||
|
||||
let myVar = {
|
||||
num: 1,
|
||||
str: "Global",
|
||||
|
||||
obj: {
|
||||
obj: {
|
||||
obj: { num: 10 },
|
||||
obj2: { obj3: {} },
|
||||
}
|
||||
},
|
||||
obj2: {
|
||||
obj3: {}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
myVar["self"] = myVar;
|
||||
myVar.obj["toMyVar"] = myVar;
|
||||
|
||||
|
||||
console.log('TestCode: BEFORE-VERIFYING-VARIABLES');
|
||||
|
||||
debugger; // Pause here to verify that we can see the values and types of all the variables
|
||||
break;
|
||||
}
|
||||
console.log('TestCode: END');
|
||||
})();
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче