test(docs-infra): run tests in random order (and make them pass) (#31527)
This commit updates the necessary config files to run the angular.io and docs tooling unit tests in random order (and fixes the tests that were failing due to their dependence on the previous ordered execution). Besides being a good idea anyway, running tests in random order is the new [default behavior in jasmine@3.0.0][1], so this commit is in preparation of upgrading jasmine to the latest version. [1]: https://github.com/jasmine/jasmine/blob/v3.0.0/release_notes/3.0.md#breaking-changes PR Close #31527
This commit is contained in:
parent
e8ae3c5f2e
commit
d7ca263cc4
|
@ -10,17 +10,22 @@ module.exports = function (config) {
|
|||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
{'reporter:jasmine-seed': ['type', JasmineSeedReporter]},
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
jasmine: {
|
||||
random: true,
|
||||
seed: '',
|
||||
},
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/site'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
fixWebpackSourcePaths: true,
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
reporters: ['progress', 'kjhtml', 'jasmine-seed'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
|
@ -28,6 +33,18 @@ module.exports = function (config) {
|
|||
browsers: ['Chrome'],
|
||||
browserNoActivityTimeout: 60000,
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
|
||||
// Helpers
|
||||
function JasmineSeedReporter(baseReporterDecorator) {
|
||||
baseReporterDecorator(this);
|
||||
|
||||
this.onBrowserComplete = (browser, result) => {
|
||||
const seed = result.order && result.order.random && result.order.seed;
|
||||
if (seed) this.write(`${browser}: Randomized with seed ${seed}.\n`);
|
||||
};
|
||||
|
||||
this.onRunComplete = () => undefined;
|
||||
}
|
||||
|
|
|
@ -630,6 +630,11 @@ describe('AppComponent', () => {
|
|||
};
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
tocContainer = null;
|
||||
toc = null;
|
||||
});
|
||||
|
||||
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
|
|
|
@ -5,6 +5,8 @@ import { CodeExampleComponent } from './code-example.component';
|
|||
import { CodeExampleModule } from './code-example.module';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
describe('CodeExampleComponent', () => {
|
||||
let hostComponent: HostComponent;
|
||||
|
@ -19,6 +21,7 @@ describe('CodeExampleComponent', () => {
|
|||
],
|
||||
providers: [
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { CodeTabsComponent } from './code-tabs.component';
|
||||
import { CodeTabsModule } from './code-tabs.module';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
describe('CodeTabsComponent', () => {
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
|
@ -19,6 +21,7 @@ describe('CodeTabsComponent', () => {
|
|||
schemas: [ NO_ERRORS_SCHEMA ],
|
||||
providers: [
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
@ -1,114 +1,104 @@
|
|||
import { Component, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { CodeComponent } from './code.component';
|
||||
import { CodeModule } from './code.module';
|
||||
import { CopierService } from 'app/shared//copier.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockPrettyPrinter } from 'testing/pretty-printer.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
const oneLineCode = 'const foo = "bar";';
|
||||
|
||||
const smallMultiLineCode = `
|
||||
<hero-details>
|
||||
const smallMultiLineCode =
|
||||
`<hero-details>
|
||||
<h2>Bah Dah Bing</h2>
|
||||
<hero-team>
|
||||
<h3>NYC Team</h3>
|
||||
</hero-team>
|
||||
</hero-details>`;
|
||||
|
||||
const bigMultiLineCode = smallMultiLineCode + smallMultiLineCode + smallMultiLineCode;
|
||||
const bigMultiLineCode = `${smallMultiLineCode}\n${smallMultiLineCode}\n${smallMultiLineCode}`;
|
||||
|
||||
describe('CodeComponent', () => {
|
||||
let hostComponent: HostComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
|
||||
// WARNING: Chance of cross-test pollution
|
||||
// CodeComponent injects PrettyPrintService
|
||||
// Once PrettyPrintService runs once _anywhere_, its ctor loads `prettify.js`
|
||||
// which sets `window['prettyPrintOne']`
|
||||
// That global survives these tests unless
|
||||
// we take strict measures to wipe it out in the `afterAll`
|
||||
// and make sure THAT runs after the tests by making component creation async
|
||||
afterAll(() => {
|
||||
delete (window as any)['prettyPrint'];
|
||||
delete (window as any)['prettyPrintOne'];
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ NoopAnimationsModule, CodeModule ],
|
||||
declarations: [ HostComponent ],
|
||||
providers: [
|
||||
PrettyPrinter,
|
||||
CopierService,
|
||||
{provide: Logger, useClass: TestLogger }
|
||||
{ provide: Logger, useClass: TestLogger },
|
||||
{ provide: PrettyPrinter, useClass: MockPrettyPrinter },
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
});
|
||||
|
||||
// Must be async because
|
||||
// CodeComponent creates PrettyPrintService which async loads `prettify.js`.
|
||||
// If not async, `afterAll` finishes before tests do!
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
hostComponent = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('pretty printing', () => {
|
||||
const untilCodeFormatted = () => {
|
||||
const emitter = hostComponent.codeComponent.codeFormatted;
|
||||
return emitter.pipe(first()).toPromise();
|
||||
};
|
||||
const hasLineNumbers = async () => {
|
||||
// presence of `<li>`s are a tell-tale for line numbers
|
||||
await untilCodeFormatted();
|
||||
return 0 < fixture.nativeElement.querySelectorAll('li').length;
|
||||
};
|
||||
const getFormattedCode = () => fixture.nativeElement.querySelector('code').innerHTML;
|
||||
|
||||
it('should format a one-line code sample', async () => {
|
||||
it('should format a one-line code sample without linenums by default', () => {
|
||||
hostComponent.setCode(oneLineCode);
|
||||
await untilCodeFormatted();
|
||||
|
||||
// 'pln' spans are a tell-tale for syntax highlighting
|
||||
const spans = fixture.nativeElement.querySelectorAll('span.pln');
|
||||
expect(spans.length).toBeGreaterThan(0, 'formatted spans');
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${oneLineCode}`);
|
||||
});
|
||||
|
||||
it('should format a one-line code sample without linenums by default', async () => {
|
||||
it('should add line numbers to one-line code sample when linenums is `true`', () => {
|
||||
hostComponent.setCode(oneLineCode);
|
||||
expect(await hasLineNumbers()).toBe(false);
|
||||
hostComponent.linenums = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: true): ${oneLineCode}`);
|
||||
});
|
||||
|
||||
it('should add line numbers to one-line code sample when linenums set true', async () => {
|
||||
it('should add line numbers to one-line code sample when linenums is `\'true\'`', () => {
|
||||
hostComponent.setCode(oneLineCode);
|
||||
hostComponent.linenums = 'true';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(await hasLineNumbers()).toBe(true);
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: true): ${oneLineCode}`);
|
||||
});
|
||||
|
||||
it('should format a small multi-line code without linenums by default', async () => {
|
||||
hostComponent.setCode(smallMultiLineCode);
|
||||
expect(await hasLineNumbers()).toBe(false);
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${smallMultiLineCode}`);
|
||||
});
|
||||
|
||||
it('should add line numbers to a big multi-line code by default', async () => {
|
||||
hostComponent.setCode(bigMultiLineCode);
|
||||
expect(await hasLineNumbers()).toBe(true);
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: true): ${bigMultiLineCode}`);
|
||||
});
|
||||
|
||||
it('should format big multi-line code without linenums when linenums set false', async () => {
|
||||
it('should format big multi-line code without linenums when linenums is `false`', async () => {
|
||||
hostComponent.setCode(bigMultiLineCode);
|
||||
hostComponent.linenums = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${bigMultiLineCode}`);
|
||||
});
|
||||
|
||||
it('should format big multi-line code without linenums when linenums is `\'false\'`', async () => {
|
||||
hostComponent.setCode(bigMultiLineCode);
|
||||
expect(await hasLineNumbers()).toBe(false);
|
||||
hostComponent.linenums = 'false';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getFormattedCode()).toBe(
|
||||
`Formatted code (language: auto, linenums: false): ${bigMultiLineCode}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -117,9 +107,16 @@ describe('CodeComponent', () => {
|
|||
hostComponent.linenums = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
hostComponent.setCode(' abc\n let x = text.split(\'\\n\');\n ghi\n\n jkl\n');
|
||||
hostComponent.setCode(`
|
||||
abc
|
||||
let x = text.split('\\n');
|
||||
ghi
|
||||
|
||||
jkl
|
||||
`);
|
||||
const codeContent = fixture.nativeElement.querySelector('code').textContent;
|
||||
expect(codeContent).toEqual('abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
|
||||
expect(codeContent).toEqual(
|
||||
'Formatted code (language: auto, linenums: false): abc\n let x = text.split(\'\\n\');\nghi\n\njkl');
|
||||
});
|
||||
|
||||
it('should trim whitespace from the code before rendering', () => {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// The actual `PrettyPrinter` service has to load `prettify.js`, which (a) is slow and (b) pollutes
|
||||
// the global `window` object (which in turn may affect other tests).
|
||||
// This is a mock implementation that does not load `prettify.js` and does not pollute the global
|
||||
// scope.
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
export class MockPrettyPrinter {
|
||||
formatCode(code: string, language?: string, linenums?: number | boolean) {
|
||||
const linenumsStr = (linenums === undefined) ? '' : `, linenums: ${linenums}`;
|
||||
return of(`Formatted code (language: ${language || 'auto'}${linenumsStr}): ${code}`);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ var Dgeni = require('dgeni');
|
|||
describe('convertToJson processor', () => {
|
||||
var dgeni, injector, processor, log;
|
||||
|
||||
beforeAll(function() {
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular-base-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('convertToJsonProcessor');
|
||||
|
@ -72,4 +72,4 @@ describe('convertToJson processor', () => {
|
|||
processor.$process(docs);
|
||||
expect(log.warn).toHaveBeenCalledWith('Title property expected - doc (test-doc) ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,13 +2,12 @@ var testPackage = require('../../helpers/test-package');
|
|||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('renderExamples processor', () => {
|
||||
var injector, processor, exampleMap, collectExamples, log;
|
||||
var injector, processor, collectExamples, exampleMap, log;
|
||||
|
||||
beforeEach(function() {
|
||||
const dgeni = new Dgeni([testPackage('examples-package', true)]);
|
||||
injector = dgeni.configureInjector();
|
||||
|
||||
exampleMap = injector.get('exampleMap');
|
||||
processor = injector.get('renderExamples');
|
||||
collectExamples = injector.get('collectExamples');
|
||||
exampleMap = injector.get('exampleMap');
|
||||
|
|
|
@ -7,113 +7,113 @@ const DEFAULT_PLASTER = '. . .';
|
|||
const {mapObject} = require('../../helpers/utils');
|
||||
|
||||
module.exports = function regionParser() {
|
||||
regionParserImpl.regionMatchers = {
|
||||
ts: inlineC,
|
||||
js: inlineC,
|
||||
es6: inlineC,
|
||||
dart: inlineC,
|
||||
html: html,
|
||||
svg: html,
|
||||
css: blockC,
|
||||
yaml: inlineHash,
|
||||
yml: inlineHash,
|
||||
jade: inlineCOnly,
|
||||
pug: inlineCOnly,
|
||||
json: inlineC,
|
||||
'json.annotated': inlineC
|
||||
};
|
||||
|
||||
return regionParserImpl;
|
||||
};
|
||||
|
||||
regionParserImpl.regionMatchers = {
|
||||
ts: inlineC,
|
||||
js: inlineC,
|
||||
es6: inlineC,
|
||||
dart: inlineC,
|
||||
html: html,
|
||||
svg: html,
|
||||
css: blockC,
|
||||
yaml: inlineHash,
|
||||
yml: inlineHash,
|
||||
jade: inlineCOnly,
|
||||
pug: inlineCOnly,
|
||||
json: inlineC,
|
||||
'json.annotated': inlineC
|
||||
};
|
||||
/**
|
||||
* @param contents string
|
||||
* @param fileType string
|
||||
* @returns {contents: string, regions: {[regionName: string]: string}}
|
||||
*/
|
||||
function regionParserImpl(contents, fileType) {
|
||||
const regionMatcher = regionParserImpl.regionMatchers[fileType];
|
||||
const openRegions = [];
|
||||
const regionMap = {};
|
||||
|
||||
/**
|
||||
* @param contents string
|
||||
* @param fileType string
|
||||
* @returns {contents: string, regions: {[regionName: string]: string}}
|
||||
*/
|
||||
function regionParserImpl(contents, fileType) {
|
||||
const regionMatcher = regionParserImpl.regionMatchers[fileType];
|
||||
const openRegions = [];
|
||||
const regionMap = {};
|
||||
if (regionMatcher) {
|
||||
let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER);
|
||||
const lines = contents.split(/\r?\n/).filter((line, index) => {
|
||||
const startRegion = line.match(regionMatcher.regionStartMatcher);
|
||||
const endRegion = line.match(regionMatcher.regionEndMatcher);
|
||||
const updatePlaster = line.match(regionMatcher.plasterMatcher);
|
||||
|
||||
if (regionMatcher) {
|
||||
let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER);
|
||||
const lines = contents.split(/\r?\n/).filter((line, index) => {
|
||||
const startRegion = line.match(regionMatcher.regionStartMatcher);
|
||||
const endRegion = line.match(regionMatcher.regionEndMatcher);
|
||||
const updatePlaster = line.match(regionMatcher.plasterMatcher);
|
||||
// start region processing
|
||||
if (startRegion) {
|
||||
// open up the specified region
|
||||
const regionNames = getRegionNames(startRegion[1]);
|
||||
if (regionNames.length === 0) {
|
||||
regionNames.push('');
|
||||
}
|
||||
regionNames.forEach(regionName => {
|
||||
const region = regionMap[regionName];
|
||||
if (region) {
|
||||
if (region.open) {
|
||||
throw new RegionParserError(
|
||||
`Tried to open a region, named "${regionName}", that is already open`, index);
|
||||
}
|
||||
region.open = true;
|
||||
if (plaster) {
|
||||
region.lines.push(plaster);
|
||||
}
|
||||
} else {
|
||||
regionMap[regionName] = {lines: [], open: true};
|
||||
}
|
||||
openRegions.push(regionName);
|
||||
});
|
||||
|
||||
// start region processing
|
||||
if (startRegion) {
|
||||
// open up the specified region
|
||||
const regionNames = getRegionNames(startRegion[1]);
|
||||
if (regionNames.length === 0) {
|
||||
regionNames.push('');
|
||||
}
|
||||
regionNames.forEach(regionName => {
|
||||
const region = regionMap[regionName];
|
||||
if (region) {
|
||||
if (region.open) {
|
||||
// end region processing
|
||||
} else if (endRegion) {
|
||||
if (openRegions.length === 0) {
|
||||
throw new RegionParserError('Tried to close a region when none are open', index);
|
||||
}
|
||||
// close down the specified region (or most recent if no name is given)
|
||||
const regionNames = getRegionNames(endRegion[1]);
|
||||
if (regionNames.length === 0) {
|
||||
regionNames.push(openRegions[openRegions.length - 1]);
|
||||
}
|
||||
|
||||
regionNames.forEach(regionName => {
|
||||
const region = regionMap[regionName];
|
||||
if (!region || !region.open) {
|
||||
throw new RegionParserError(
|
||||
`Tried to open a region, named "${regionName}", that is already open`, index);
|
||||
`Tried to close a region, named "${regionName}", that is not open`, index);
|
||||
}
|
||||
region.open = true;
|
||||
if (plaster) {
|
||||
region.lines.push(plaster);
|
||||
}
|
||||
} else {
|
||||
regionMap[regionName] = {lines: [], open: true};
|
||||
}
|
||||
openRegions.push(regionName);
|
||||
});
|
||||
region.open = false;
|
||||
removeLast(openRegions, regionName);
|
||||
});
|
||||
|
||||
// end region processing
|
||||
} else if (endRegion) {
|
||||
if (openRegions.length === 0) {
|
||||
throw new RegionParserError('Tried to close a region when none are open', index);
|
||||
}
|
||||
// close down the specified region (or most recent if no name is given)
|
||||
const regionNames = getRegionNames(endRegion[1]);
|
||||
if (regionNames.length === 0) {
|
||||
regionNames.push(openRegions[openRegions.length - 1]);
|
||||
// doc plaster processing
|
||||
} else if (updatePlaster) {
|
||||
const plasterString = updatePlaster[1].trim();
|
||||
plaster = plasterString ? regionMatcher.createPlasterComment(plasterString) : '';
|
||||
|
||||
// simple line of content processing
|
||||
} else {
|
||||
openRegions.forEach(regionName => regionMap[regionName].lines.push(line));
|
||||
// do not filter out this line from the content
|
||||
return true;
|
||||
}
|
||||
|
||||
regionNames.forEach(regionName => {
|
||||
const region = regionMap[regionName];
|
||||
if (!region || !region.open) {
|
||||
throw new RegionParserError(
|
||||
`Tried to close a region, named "${regionName}", that is not open`, index);
|
||||
}
|
||||
region.open = false;
|
||||
removeLast(openRegions, regionName);
|
||||
});
|
||||
|
||||
// doc plaster processing
|
||||
} else if (updatePlaster) {
|
||||
const plasterString = updatePlaster[1].trim();
|
||||
plaster = plasterString ? regionMatcher.createPlasterComment(plasterString) : '';
|
||||
|
||||
// simple line of content processing
|
||||
} else {
|
||||
openRegions.forEach(regionName => regionMap[regionName].lines.push(line));
|
||||
// do not filter out this line from the content
|
||||
return true;
|
||||
// this line contained an annotation so let's filter it out
|
||||
return false;
|
||||
});
|
||||
if (!regionMap['']) {
|
||||
regionMap[''] = {lines};
|
||||
}
|
||||
|
||||
// this line contained an annotation so let's filter it out
|
||||
return false;
|
||||
});
|
||||
if (!regionMap['']) {
|
||||
regionMap[''] = {lines};
|
||||
return {
|
||||
contents: lines.join('\n'),
|
||||
regions: mapObject(regionMap, (regionName, region) => leftAlign(region.lines).join('\n'))
|
||||
};
|
||||
} else {
|
||||
return {contents, regions: {}};
|
||||
}
|
||||
return {
|
||||
contents: lines.join('\n'),
|
||||
regions: mapObject(regionMap, (regionName, region) => leftAlign(region.lines).join('\n'))
|
||||
};
|
||||
} else {
|
||||
return {contents, regions: {}};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getRegionNames(input) {
|
||||
return (input.trim() === '') ? [] : input.split(',').map(name => name.trim());
|
||||
|
|
|
@ -13,5 +13,5 @@
|
|||
|
||||
const Jasmine = require('jasmine');
|
||||
const jasmine = new Jasmine({ projectBaseDir: __dirname });
|
||||
jasmine.loadConfig({ spec_files: ['**/*.spec.js'] });
|
||||
jasmine.loadConfig({ random: true, spec_files: ['**/*.spec.js'] });
|
||||
jasmine.execute();
|
||||
|
|
Loading…
Reference in New Issue