mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 10:33:59 -05:00
Unit testing the frontend (#7829)
This adds some unit tests for the front end. - configures "frontend tests" as a unit tests in `static/tests`, removing the old cypress-requiring "unit" tests - hack enough of a DOM to get things working - port motd and id tests - *adds* a golden layout checks (see #7807) - Updates READMEs etc --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +0,0 @@
|
|||||||
import {runFrontendTest} from '../support/utils';
|
|
||||||
|
|
||||||
describe('Motd testing', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.visit('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
runFrontendTest('motd');
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import {runFrontendTest} from '../support/utils';
|
|
||||||
|
|
||||||
describe('RemoteId testing', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.visit('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
runFrontendTest('remoteId');
|
|
||||||
});
|
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
import '../../static/global';
|
import '../../static/global';
|
||||||
|
|
||||||
export function runFrontendTest(name: string) {
|
|
||||||
it(name, () => {
|
|
||||||
return cy.window().then(win => {
|
|
||||||
return win.compilerExplorerFrontendTesting.run(name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stubConsoleOutput(win: Cypress.AUTWindow) {
|
export function stubConsoleOutput(win: Cypress.AUTWindow) {
|
||||||
cy.stub(win.console, 'log').as('consoleLog');
|
cy.stub(win.console, 'log').as('consoleLog');
|
||||||
cy.stub(win.console, 'warn').as('consoleWarn');
|
cy.stub(win.console, 'warn').as('consoleWarn');
|
||||||
|
|||||||
@@ -1,34 +1,12 @@
|
|||||||
# Frontend testing
|
# Frontend testing
|
||||||
|
|
||||||
We have a mixture of typescript in the main website's code (located in `static/tests`) and Cypress (located in
|
We have a mixture of unit tests (located in `static/tests`) and Cypress UI tests(located in `cypress/e2e`).
|
||||||
`cypress/e2e`) to test and report on the workings of that code.
|
|
||||||
|
|
||||||
But there's always the possibility to use Cypress code to do UI checks and testing.
|
If possible, create unit tests in `static/tests` for the code you are working on. If you can get away with simple state
|
||||||
|
tests and don't need to do any real DOM manipulation, this is the way to go. Testing "does this filter correctly" or "do
|
||||||
|
we parse this right" are perfect examples of this. These tests use `happy-dom` for _extremely minimal_ DOM mocking just
|
||||||
|
enough to get the code running at all.
|
||||||
|
|
||||||
## Recommended
|
If you need to check actual behaviour or rely on the pug loading/HTML rendering, you should use the Cypress tests.
|
||||||
|
|
||||||
The recommended way of testing is to use typescript to test the inner workings of the various interfaces that are
|
To run the cypress tests, see [this document](../UsingCypress.md).
|
||||||
available.
|
|
||||||
|
|
||||||
This has the advantage of having types and being able to verify your code is consistent with the rest of the website and
|
|
||||||
probably going to run correctly - without having to startup the website and Cypress.
|
|
||||||
|
|
||||||
## Adding a test
|
|
||||||
|
|
||||||
Steps to add a test:
|
|
||||||
|
|
||||||
- Create a new file in `static/tests` (copy paste from `static/tests/hello-world.ts`)
|
|
||||||
- Make sure to change the `description` as well as the test
|
|
||||||
- Add the file to the imports of `static/tests/_all.ts`
|
|
||||||
- Add a test file with a `runFrontendTest()` call in `cypress/e2e`
|
|
||||||
|
|
||||||
## Starting tests locally
|
|
||||||
|
|
||||||
You don't need to install an entire X server to actually run cypress (just xfvb).
|
|
||||||
|
|
||||||
You can find a complete list at https://docs.cypress.io/guides/getting-started/installing-cypress#System-requirements
|
|
||||||
|
|
||||||
If you have the prerequisites installed, you should be able to run `npx cypress run` - however, you will need to start
|
|
||||||
the CE website separately in another terminal before that.
|
|
||||||
|
|
||||||
Some extra tips can be found [here](../UsingCypress.md)
|
|
||||||
|
|||||||
1603
package-lock.json
generated
1603
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -111,12 +111,14 @@
|
|||||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||||
"cypress": "^14.4.1",
|
"cypress": "^14.4.1",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
|
"happy-dom": "^18.0.1",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.1.0",
|
"lint-staged": "^16.1.0",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"mock-fs": "^5.5.0",
|
"mock-fs": "^5.5.0",
|
||||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||||
"nock": "^14.0.5",
|
"nock": "^14.0.5",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.89.2",
|
||||||
"sass-loader": "^16.0.5",
|
"sass-loader": "^16.0.5",
|
||||||
"source-map-loader": "^5.0.0",
|
"source-map-loader": "^5.0.0",
|
||||||
@@ -156,7 +158,11 @@
|
|||||||
"update-browserslist": "npx browserslist@latest -- --update-db",
|
"update-browserslist": "npx browserslist@latest -- --update-db",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"ts-compile": "tsc",
|
"ts-compile": "tsc",
|
||||||
"ts-check": "tsc -p ./tsconfig.backend.json --noEmit && tsc -p ./tsconfig.frontend.json --noEmit && tsc -p ./tsconfig.tests.json --noEmit",
|
"ts-check": "npm-run-all ts-check:*",
|
||||||
|
"ts-check:backend": "tsc -p ./tsconfig.backend.json --noEmit",
|
||||||
|
"ts-check:frontend": "tsc -p ./tsconfig.frontend.json --noEmit",
|
||||||
|
"ts-check:tests": "tsc -p ./tsconfig.tests.json --noEmit",
|
||||||
|
"ts-check:frontend-tests": "tsc -p ./tsconfig.frontend.tests.json --noEmit",
|
||||||
"webpack": "node --no-warnings=ExperimentalWarning --import=tsx ./node_modules/webpack-cli/bin/cli.js --node-env=production --config webpack.config.esm.ts"
|
"webpack": "node --no-warnings=ExperimentalWarning --import=tsx ./node_modules/webpack-cli/bin/cli.js --node-env=production --config webpack.config.esm.ts"
|
||||||
},
|
},
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import {Options} from './options.interfaces.js';
|
import {Options} from './options.interfaces.js';
|
||||||
import {IFrontendTesting} from './tests/frontend-testing.interfaces.js';
|
|
||||||
|
|
||||||
export type CompilerExplorerOptions = Record<string, unknown> & Options;
|
export type CompilerExplorerOptions = Record<string, unknown> & Options;
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ declare global {
|
|||||||
httpRoot: string;
|
httpRoot: string;
|
||||||
staticRoot: string;
|
staticRoot: string;
|
||||||
compilerExplorerOptions: CompilerExplorerOptions;
|
compilerExplorerOptions: CompilerExplorerOptions;
|
||||||
compilerExplorerFrontendTesting: IFrontendTesting;
|
|
||||||
hasUIBeenReset: boolean;
|
hasUIBeenReset: boolean;
|
||||||
PRODUCTION: boolean;
|
PRODUCTION: boolean;
|
||||||
onSponsorClick: (sponsorUrl: string) => void;
|
onSponsorClick: (sponsorUrl: string) => void;
|
||||||
|
|||||||
@@ -76,11 +76,6 @@ import changelogDocument from './generated/changelog.pug';
|
|||||||
import cookiesDocument from './generated/cookies.pug';
|
import cookiesDocument from './generated/cookies.pug';
|
||||||
import privacyDocument from './generated/privacy.pug';
|
import privacyDocument from './generated/privacy.pug';
|
||||||
|
|
||||||
if (!window.PRODUCTION && !options.embedded) {
|
|
||||||
// TODO: Replace with top-level await import() when we move to Vite
|
|
||||||
require('./tests/_all');
|
|
||||||
}
|
|
||||||
|
|
||||||
//css
|
//css
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import 'golden-layout/src/css/goldenlayout-base.css';
|
import 'golden-layout/src/css/goldenlayout-base.css';
|
||||||
|
|||||||
5
static/tests/README.md
Normal file
5
static/tests/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## Front-end unit tests
|
||||||
|
|
||||||
|
Tests here are _super simple_ and use `jsdom` with a fake document to do quick checks on the front-end code.
|
||||||
|
|
||||||
|
For anything requiring a real browser, use the `cypress` tests in the `cypress` directory.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2023, Compiler Explorer Authors
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
@@ -22,7 +22,5 @@
|
|||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import './frontend-testing';
|
globalThis.__webpack_public_path__ = '';
|
||||||
import './hello-world';
|
document.body.innerHTML = '<div id="config" httpRoot="/test/" staticRoot="/test/static/" extraOptions="{}"></div>';
|
||||||
import './motd';
|
|
||||||
import './remote-id';
|
|
||||||
682
static/tests/components-tests.ts
Normal file
682
static/tests/components-tests.ts
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import {describe, expect, it} from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createComponentConfig,
|
||||||
|
createLayoutItem,
|
||||||
|
fromGoldenLayoutConfig,
|
||||||
|
toGoldenLayoutConfig,
|
||||||
|
} from '../../static/components.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
COMPILER_COMPONENT_NAME,
|
||||||
|
DIFF_VIEW_COMPONENT_NAME,
|
||||||
|
EDITOR_COMPONENT_NAME,
|
||||||
|
EXECUTOR_COMPONENT_NAME,
|
||||||
|
OPT_VIEW_COMPONENT_NAME,
|
||||||
|
OUTPUT_COMPONENT_NAME,
|
||||||
|
TOOL_COMPONENT_NAME,
|
||||||
|
TREE_COMPONENT_NAME,
|
||||||
|
} from '../../static/components.interfaces.js';
|
||||||
|
|
||||||
|
describe('Components validation', () => {
|
||||||
|
describe('fromGoldenLayoutConfig', () => {
|
||||||
|
describe('Input validation', () => {
|
||||||
|
it('should throw for null input', () => {
|
||||||
|
expect(() => fromGoldenLayoutConfig(null as any)).toThrow('Invalid configuration: must be an object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for undefined input', () => {
|
||||||
|
expect(() => fromGoldenLayoutConfig(undefined as any)).toThrow(
|
||||||
|
'Invalid configuration: must be an object',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for non-object input', () => {
|
||||||
|
expect(() => fromGoldenLayoutConfig('string' as any)).toThrow(
|
||||||
|
'Invalid configuration: must be an object',
|
||||||
|
);
|
||||||
|
expect(() => fromGoldenLayoutConfig(123 as any)).toThrow('Invalid configuration: must be an object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept arrays as valid objects', () => {
|
||||||
|
// Arrays are objects in JavaScript, so they pass the initial validation
|
||||||
|
// The content validation will handle any structure issues later
|
||||||
|
expect(() => fromGoldenLayoutConfig([] as any)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Valid configurations', () => {
|
||||||
|
it('should accept empty configuration', () => {
|
||||||
|
const config = {};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept configuration with empty content array', () => {
|
||||||
|
const config = {content: []};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid compiler component', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
source: 1,
|
||||||
|
lang: 'c++',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid editor component', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
id: 1,
|
||||||
|
lang: 'c++',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid executor component', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EXECUTOR_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
source: 1,
|
||||||
|
lang: 'c++',
|
||||||
|
compilationPanelShown: true,
|
||||||
|
compilerOutShown: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid output component', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: OUTPUT_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
tree: 1,
|
||||||
|
compiler: 2,
|
||||||
|
editor: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid tool component', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: TOOL_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
tree: 1,
|
||||||
|
toolId: 'readelf',
|
||||||
|
id: 2,
|
||||||
|
editorid: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept valid layout items', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'column',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {source: 1, lang: 'c++'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid content structure', () => {
|
||||||
|
it('should throw for non-array content', () => {
|
||||||
|
const config = {content: 'not-array'} as any;
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow('Configuration content must be an array');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for content with non-object items', () => {
|
||||||
|
const config = {content: ['string-item']} as any;
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow('Invalid item 0: must be an object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for items missing type', () => {
|
||||||
|
const config = {content: [{}]} as any;
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow("Invalid item 0: missing 'type' property");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for items with unknown type', () => {
|
||||||
|
const config = {content: [{type: 'unknown'}]} as any;
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow("Invalid item 0: unknown type 'unknown'");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid component configurations', () => {
|
||||||
|
it('should throw for component missing componentName', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: missing 'componentName' property",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for component with non-string componentName', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: 123,
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: 'componentName' must be a string",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid compiler state', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
// Missing required properties
|
||||||
|
invalidProp: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: invalid component state for component 'compiler'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid executor state missing boolean flags', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EXECUTOR_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
source: 1,
|
||||||
|
lang: 'c++',
|
||||||
|
// Missing compilationPanelShown and compilerOutShown
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: invalid component state for component 'executor'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid output state missing numeric properties', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: OUTPUT_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
tree: 'not-number',
|
||||||
|
compiler: 1,
|
||||||
|
editor: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: invalid component state for component 'output'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid tool state', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: TOOL_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
tree: 1,
|
||||||
|
// Missing required toolId, id, editorid
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: invalid component state for component 'tool'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for unknown component name', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'unknown-component',
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: invalid component state for component 'unknown-component'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid layout item configurations', () => {
|
||||||
|
it('should throw for layout item missing content', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any;
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: layout items must have a 'content' array",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for layout item with non-array content', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'column',
|
||||||
|
content: 'not-array',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any;
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: layout items must have a 'content' array",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Complex nested validation', () => {
|
||||||
|
it('should validate deeply nested structures', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'column',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'stack',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: DIFF_VIEW_COMPONENT_NAME,
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {source: 1, lang: 'c++'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for invalid nested component', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'column',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
// Invalid state - missing required properties
|
||||||
|
invalidProp: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow('invalid component state for component');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge cases', () => {
|
||||||
|
it('should handle null component state', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).toThrow(
|
||||||
|
"Invalid item 0: invalid component state for component 'compiler'",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve additional properties', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {},
|
||||||
|
title: 'Custom Title',
|
||||||
|
isClosable: false,
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
showPopoutIcon: false,
|
||||||
|
},
|
||||||
|
maximisedItemId: null,
|
||||||
|
};
|
||||||
|
const result = fromGoldenLayoutConfig(config);
|
||||||
|
expect(result).toEqual(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed valid and invalid compiler states', () => {
|
||||||
|
// Test different valid combinations for compiler
|
||||||
|
const validConfigs = [
|
||||||
|
{source: 1, lang: 'c++'},
|
||||||
|
{source: 1, compiler: 'gcc'},
|
||||||
|
{tree: 1, lang: 'c++'},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const state of validConfigs) {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: state,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => fromGoldenLayoutConfig(config)).not.toThrow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Helper functions', () => {
|
||||||
|
describe('createComponentConfig', () => {
|
||||||
|
it('should create valid component config', () => {
|
||||||
|
const config = createComponentConfig(EDITOR_COMPONENT_NAME, {id: 1, lang: 'c++'});
|
||||||
|
expect(config).toEqual({
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {id: 1, lang: 'c++'},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include optional properties', () => {
|
||||||
|
const config = createComponentConfig(
|
||||||
|
COMPILER_COMPONENT_NAME,
|
||||||
|
{source: 1, lang: 'c++'},
|
||||||
|
{
|
||||||
|
title: 'Custom Compiler',
|
||||||
|
isClosable: false,
|
||||||
|
width: 50,
|
||||||
|
height: 200,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(config).toEqual({
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {source: 1, lang: 'c++'},
|
||||||
|
title: 'Custom Compiler',
|
||||||
|
isClosable: false,
|
||||||
|
width: 50,
|
||||||
|
height: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createLayoutItem', () => {
|
||||||
|
it('should create valid layout item', () => {
|
||||||
|
const content = [
|
||||||
|
createComponentConfig(EDITOR_COMPONENT_NAME, {}),
|
||||||
|
createComponentConfig(COMPILER_COMPONENT_NAME, {source: 1, lang: 'c++'}),
|
||||||
|
];
|
||||||
|
const layoutItem = createLayoutItem('row', content);
|
||||||
|
expect(layoutItem).toEqual({
|
||||||
|
type: 'row',
|
||||||
|
content,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include optional properties', () => {
|
||||||
|
const content = [createComponentConfig(DIFF_VIEW_COMPONENT_NAME, {})];
|
||||||
|
const layoutItem = createLayoutItem('stack', content, {
|
||||||
|
isClosable: true,
|
||||||
|
width: 100,
|
||||||
|
activeItemIndex: 0,
|
||||||
|
});
|
||||||
|
expect(layoutItem).toEqual({
|
||||||
|
type: 'stack',
|
||||||
|
content,
|
||||||
|
isClosable: true,
|
||||||
|
width: 100,
|
||||||
|
activeItemIndex: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toGoldenLayoutConfig', () => {
|
||||||
|
it('should pass through config unchanged', () => {
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
createLayoutItem('row', [
|
||||||
|
createComponentConfig(EDITOR_COMPONENT_NAME, {id: 1}),
|
||||||
|
createComponentConfig(COMPILER_COMPONENT_NAME, {source: 1, lang: 'c++'}),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
settings: {showPopoutIcon: false},
|
||||||
|
};
|
||||||
|
const result = toGoldenLayoutConfig(config);
|
||||||
|
expect(result).toBe(config);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Integration tests', () => {
|
||||||
|
it('should round-trip through validation', () => {
|
||||||
|
const originalConfig = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {id: 1, lang: 'c++'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {source: 1, lang: 'c++'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
settings: {showPopoutIcon: false},
|
||||||
|
};
|
||||||
|
|
||||||
|
const validated = fromGoldenLayoutConfig(originalConfig);
|
||||||
|
const backToGolden = toGoldenLayoutConfig(validated);
|
||||||
|
|
||||||
|
expect(backToGolden).toEqual(originalConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with complex real-world config', () => {
|
||||||
|
const complexConfig = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'row',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'column',
|
||||||
|
width: 50,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: EDITOR_COMPONENT_NAME,
|
||||||
|
componentState: {id: 1, lang: 'c++', source: '#include <iostream>'},
|
||||||
|
height: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: TREE_COMPONENT_NAME,
|
||||||
|
componentState: {id: 1, cmakeArgs: '-DCMAKE_BUILD_TYPE=Release'},
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'stack',
|
||||||
|
width: 50,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: COMPILER_COMPONENT_NAME,
|
||||||
|
componentState: {
|
||||||
|
source: 1,
|
||||||
|
lang: 'c++',
|
||||||
|
compiler: 'gcc',
|
||||||
|
options: '-O2',
|
||||||
|
},
|
||||||
|
title: 'GCC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'component',
|
||||||
|
componentName: OPT_VIEW_COMPONENT_NAME,
|
||||||
|
componentState: {id: 1, source: 'test'},
|
||||||
|
title: 'Optimization',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
showPopoutIcon: false,
|
||||||
|
showMaximiseIcon: true,
|
||||||
|
showCloseIcon: true,
|
||||||
|
},
|
||||||
|
maximisedItemId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() => fromGoldenLayoutConfig(complexConfig)).not.toThrow();
|
||||||
|
const result = fromGoldenLayoutConfig(complexConfig);
|
||||||
|
expect(result).toEqual(complexConfig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) 2022, Compiler Explorer Authors
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer in the
|
|
||||||
// documentation and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
export interface ITestable {
|
|
||||||
readonly description: string;
|
|
||||||
run(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFrontendTesting {
|
|
||||||
add(test: ITestable): void;
|
|
||||||
getAllTestNames(): string[];
|
|
||||||
run(testToRun: string): Promise<void>;
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// Copyright (c) 2022, Compiler Explorer Authors
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer in the
|
|
||||||
// documentation and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
import {IFrontendTesting, ITestable} from './frontend-testing.interfaces.js';
|
|
||||||
|
|
||||||
class FrontendTesting implements IFrontendTesting {
|
|
||||||
private testSuites: Array<ITestable> = [];
|
|
||||||
|
|
||||||
public add(test: ITestable) {
|
|
||||||
this.testSuites.push(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAllTestNames(): string[] {
|
|
||||||
return this.testSuites.map(val => val.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
private findTest(name: string) {
|
|
||||||
for (const suite of this.testSuites) {
|
|
||||||
if (suite.description === name) {
|
|
||||||
return suite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Can't find test ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async run(testToRun: string) {
|
|
||||||
const testSuite = this.findTest(testToRun);
|
|
||||||
await testSuite.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.compilerExplorerFrontendTesting = new FrontendTesting();
|
|
||||||
361
static/tests/motd-tests.ts
Normal file
361
static/tests/motd-tests.ts
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
|
|
||||||
|
import {isValidAd} from '../../static/motd.js';
|
||||||
|
|
||||||
|
describe('MOTD Tests', () => {
|
||||||
|
describe('with fake timers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if now > from', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(isValidAd({filter: [], html: '', valid_from: '2022-01-01T00:00:00+00:00'}, 'langForTest')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if now < from', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(isValidAd({filter: [], html: '', valid_from: '2022-01-16T00:00:00+00:00'}, 'langForTest')).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if now < until', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(isValidAd({filter: [], html: '', valid_until: '2022-01-16T00:00:00+00:00'}, 'langForTest')).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if now > until', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-20T00:00:00+00:00'));
|
||||||
|
expect(isValidAd({filter: [], html: '', valid_until: '2022-01-16T00:00:00+00:00'}, 'langForTest')).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if from < now < until', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-01T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if now < from < until', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-10T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if from < until < now', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-20T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-10T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if until < now < from', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-16T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-10T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if from < now < until but filtered by lang', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['fakeLang'],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-01T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if from < now < until and not filtered by lang', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['langForTest'],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-01T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if from = now < until and not filtered by lang', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['langForTest'],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-08T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if from < now = until and not filtered by lang', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-08T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['langForTest'],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-01T00:00:00+00:00',
|
||||||
|
valid_until: '2022-01-18T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad when now equals valid_until', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-16T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_until: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad when now equals valid_from', () => {
|
||||||
|
vi.setSystemTime(new Date('2022-01-16T00:00:00+00:00'));
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: '2022-01-16T00:00:00+00:00',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if sublang is not set', () => {
|
||||||
|
expect(isValidAd({filter: [], html: ''}, 'fakeLang')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if sublang is not set even if filtering for lang', () => {
|
||||||
|
expect(isValidAd({filter: ['fakeLang'], html: ''}, 'langForTest')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if no lang is set', () => {
|
||||||
|
expect(isValidAd({filter: [], html: ''}, 'langForTest')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if not the correct language', () => {
|
||||||
|
expect(isValidAd({filter: ['anotherLang'], html: ''}, 'langForTest')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if the correct language is used', () => {
|
||||||
|
expect(isValidAd({filter: ['langForTest'], html: ''}, 'langForTest')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if valid_from has invalid date format', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: 'invalid-date-format',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if valid_until has invalid date format', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_until: 'not-a-date',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if both dates have invalid format', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: [],
|
||||||
|
html: '',
|
||||||
|
valid_from: 'invalid-from',
|
||||||
|
valid_until: 'invalid-until',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad with empty string sublang', () => {
|
||||||
|
expect(isValidAd({filter: [], html: ''}, '')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad with null sublang', () => {
|
||||||
|
expect(isValidAd({filter: [], html: ''}, null as any)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad with undefined sublang', () => {
|
||||||
|
expect(isValidAd({filter: [], html: ''}, undefined as any)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad if sublang matches one of multiple filter languages', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['lang1', 'lang2', 'langForTest', 'lang3'],
|
||||||
|
html: '',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad if sublang does not match any of multiple filter languages', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['lang1', 'lang2', 'lang3'],
|
||||||
|
html: '',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter ad with invalid date and non-matching language filter', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['anotherLang'],
|
||||||
|
html: '',
|
||||||
|
valid_from: 'invalid-date',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep ad with invalid date and matching language filter', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['langForTest'],
|
||||||
|
html: '',
|
||||||
|
valid_until: 'invalid-date',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle filter with empty strings', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['', 'langForTest', ''],
|
||||||
|
html: '',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle filter with duplicate languages', () => {
|
||||||
|
expect(
|
||||||
|
isValidAd(
|
||||||
|
{
|
||||||
|
filter: ['langForTest', 'langForTest', 'langForTest'],
|
||||||
|
html: '',
|
||||||
|
},
|
||||||
|
'langForTest',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
// Copyright (c) 2022, Compiler Explorer Authors
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer in the
|
|
||||||
// documentation and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
/// <reference path="../../node_modules/cypress/types/cypress-global-vars.d.ts" />
|
|
||||||
|
|
||||||
import {isValidAd} from '../motd.js';
|
|
||||||
import {ITestable} from './frontend-testing.interfaces.js';
|
|
||||||
|
|
||||||
import {Ad} from '../motd.interfaces.js';
|
|
||||||
|
|
||||||
class MotdTests implements ITestable {
|
|
||||||
public readonly description: string = 'motd';
|
|
||||||
|
|
||||||
private static assertAd(ad: Ad, subLang: string, expected: boolean, message: string) {
|
|
||||||
if (isValidAd(ad, subLang) !== expected) {
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static assertAdWithDateNow(dateNow: number, ad: Ad, subLang: string, expected: boolean, message: string) {
|
|
||||||
const originalDateNow = Date.now;
|
|
||||||
Date.now = () => dateNow;
|
|
||||||
MotdTests.assertAd(ad, subLang, expected, message);
|
|
||||||
Date.now = originalDateNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
MotdTests.assertAd(
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
},
|
|
||||||
'fakeLang',
|
|
||||||
true,
|
|
||||||
'Keep ad if sublang is not set',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAd(
|
|
||||||
{
|
|
||||||
filter: ['fakeLang'],
|
|
||||||
html: '',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Keep ad if sublang is not set even if filtering for lang',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAd(
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if no lang is set',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAd(
|
|
||||||
{
|
|
||||||
filter: ['anotherLang'],
|
|
||||||
html: '',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filters ad if not the correct language',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAd(
|
|
||||||
{
|
|
||||||
filter: ['langForTest'],
|
|
||||||
html: '',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if the correct language is used',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-01T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if now > from',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filter ad if now < from',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if now < until',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filter ad if now > until',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-01T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if from < now < until',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-10T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filter ad if now < from < until',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-20T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-10T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filter ad if from < until < now',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: [],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-16T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-10T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filter ad if until < now < from',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: ['fakeLang'],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-01T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
false,
|
|
||||||
'Filter ad if from < now < until but filtered by lang',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: ['langForTest'],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-01T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if from < now < until and not filtered by lang',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: ['langForTest'],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-08T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-16T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if from = now < until and not filtered by lang',
|
|
||||||
);
|
|
||||||
|
|
||||||
MotdTests.assertAdWithDateNow(
|
|
||||||
Date.parse('2022-01-08T00:00:00+00:00'),
|
|
||||||
{
|
|
||||||
filter: ['langForTest'],
|
|
||||||
html: '',
|
|
||||||
valid_from: '2022-01-01T00:00:00+00:00',
|
|
||||||
valid_until: '2022-01-18T00:00:00+00:00',
|
|
||||||
},
|
|
||||||
'langForTest',
|
|
||||||
true,
|
|
||||||
'Keep ad if from < now = until and not filtered by lang',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.compilerExplorerFrontendTesting.add(new MotdTests());
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright (c) 2025, Compiler Explorer Authors
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright notice,
|
|
||||||
// this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer in the
|
|
||||||
// documentation and/or other materials provided with the distribution.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
||||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
/// <reference path="../../node_modules/cypress/types/cypress-global-vars.d.ts" />
|
|
||||||
|
|
||||||
import {getRemoteId} from '../../shared/remote-utils.js';
|
|
||||||
|
|
||||||
import {UrlTestCases} from '../../shared/url-testcases.js';
|
|
||||||
|
|
||||||
import {ITestable} from './frontend-testing.interfaces.js';
|
|
||||||
|
|
||||||
class RemoteIdTests implements ITestable {
|
|
||||||
public readonly description: string = 'remoteId';
|
|
||||||
|
|
||||||
public async run() {
|
|
||||||
UrlTestCases.forEach(testCase => {
|
|
||||||
if (getRemoteId(testCase.remoteUrl, testCase.language) !== testCase.expectedId) {
|
|
||||||
throw new Error(
|
|
||||||
`Test case failed for language: ${testCase.language}, remoteUrl: ${testCase.remoteUrl}, expectedId: ${testCase.expectedId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.compilerExplorerFrontendTesting.add(new RemoteIdTests());
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022, Compiler Explorer Authors
|
// Copyright (c) 2025, Compiler Explorer Authors
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
@@ -22,15 +22,13 @@
|
|||||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import {ITestable} from './frontend-testing.interfaces.js';
|
import {getRemoteId} from '../../shared/remote-utils.js';
|
||||||
|
|
||||||
class HelloWorldTests implements ITestable {
|
import {describe, expect, it} from 'vitest';
|
||||||
public readonly description: string = 'HelloWorld';
|
import {UrlTestCases} from '../../shared/url-testcases.js';
|
||||||
|
|
||||||
public async run() {
|
describe('Remote ID Tests', () => {
|
||||||
const person = true;
|
it.each(UrlTestCases)('Check $remoteUrl $language', testCase => {
|
||||||
if (!person) throw new Error('HelloWorldTests failed');
|
expect(getRemoteId(testCase.remoteUrl, testCase.language)).toBe(testCase.expectedId);
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
window.compilerExplorerFrontendTesting.add(new HelloWorldTests());
|
|
||||||
5
tsconfig.frontend.tests.json
Normal file
5
tsconfig.frontend.tests.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.frontend.json",
|
||||||
|
"files": ["static/global.ts"],
|
||||||
|
"include": ["static/tests/**/*.ts"],
|
||||||
|
}
|
||||||
@@ -6,8 +6,24 @@ export default defineConfig({
|
|||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
reporter: ['text', 'json', 'html'],
|
reporter: ['text', 'json', 'html'],
|
||||||
},
|
},
|
||||||
include: ['test/**/*.ts'],
|
projects: [
|
||||||
exclude: ['test/_*.ts', 'test/utils.ts'],
|
{
|
||||||
setupFiles: ['/test/_setup-fake-aws.ts', '/test/_setup-log.ts'],
|
test: {
|
||||||
|
name: 'unit',
|
||||||
|
include: ['test/**/*.ts'],
|
||||||
|
exclude: ['test/_*.ts', 'test/utils.ts'],
|
||||||
|
setupFiles: ['test/_setup-fake-aws.ts', 'test/_setup-log.ts'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: {
|
||||||
|
name: 'frontend unit',
|
||||||
|
include: ['static/tests/**/*.ts'],
|
||||||
|
exclude: ['static/tests/_*.ts'],
|
||||||
|
setupFiles: ['static/tests/_setup-dom.ts'],
|
||||||
|
environment: 'happy-dom',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user