Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ env
.yarn
*.bkp
*.log
!frontend/tests/src_test_data/**/*.log
3 changes: 2 additions & 1 deletion frontend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ site-sandbox: cleanall test_content webpack-sandbox sphinx-sandbox all_books com
@echo Building $@

site-testing: cleantest ## Build a testing version of the learn website.
FRONTEND_TESTING=true $(SPHINXBUILD) -M html $(TEST_CONTENT) \
FRONTEND_TESTING=true SRC_TEST_DIR=$(MKFILE_DIR)tests/src_test_data \
$(SPHINXBUILD) -M html $(TEST_CONTENT) \
"$(TEST_BUILDDIR)" $(SPHINXOPTS) $(O) -v -c "$(SPHINXCONF)"

BOOKS = $(wildcard $(CONTENT_DIR)/courses/*/.) $(wildcard $(CONTENT_DIR)/labs/*/.) $(wildcard $(CONTENT_DIR)/booklets/*/.)
Expand Down
26 changes: 26 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import jsdocPlugin from 'eslint-plugin-jsdoc';

export default [
jsdocPlugin.configs['flat/recommended'],
{
files: ['src/**/*.ts', 'tests/**/*.ts'],
plugins: {
'@typescript-eslint': tsPlugin,
jsdoc: jsdocPlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
},
},
rules: {
...tsPlugin.configs.recommended.rules,
'jsdoc/no-undefined-types': 'off',
'max-len': ['error', {ignoreRegExpLiterals: true}],
},
},
];
26 changes: 14 additions & 12 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@babel/cli": "^7.28.6",
"@babel/core": "^7.28.6",
"@babel/preset-env": "^7.28.6",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"c8": "^10.1.3",
"@types/ace": "^0.0.52",
"@types/chai": "^5.2.3",
"@types/chai-as-promised": "^8.0.2",
Expand Down Expand Up @@ -50,14 +50,12 @@
"html-webpack-plugin": "^5.6.6",
"ifdef-loader": "^2.3.2",
"imports-loader": "^5.0.0",
"istanbul-lib-instrument": "^6.0.3",
"jsdom": "^27.4.0",
"jsdom-global": "^3.0.2",
"mini-css-extract-plugin": "^2.10.0",
"mocha": "^11.7.5",
"mock-socket": "^9.3.1",
"node-fetch": "^3.3.2",
"nyc": "^17.1.0",
"postcss": "^8.5.6",
"postcss-loader": "^8.2.0",
"sass": "^1.97.3",
Expand Down Expand Up @@ -94,7 +92,7 @@
"sandbox": "webpack --env sandbox --config webpack.prod.cjs",
"production": "webpack --config webpack.prod.cjs",
"test": "TS_NODE_COMPILER_OPTIONS='{\"strict\":false}' mocha --exit",
"cover": "nyc yarn run test",
"cover": "c8 yarn run test",
"doc": "typedoc src --out docs"
},
"author": "AdaCore",
Expand Down Expand Up @@ -127,14 +125,15 @@
"sourceType": "module"
}
},
"nyc": {
"extends": "@istanbuljs/nyc-config-typescript",
"check-coverage": true,
"c8": {
"all": true,
"branches": ">75",
"lines": ">80",
"functions": ">80",
"statements": ">80",
"branches": 75,
"lines": 80,
"functions": 80,
"statements": 80,
"src": [
"src"
],
"include": [
"src/**/*.ts"
],
Expand All @@ -143,7 +142,10 @@
"text"
],
"exclude": [
"src/index.ts"
"src/index.ts",
"src/ts/resource.ts",
"src/ts/server-types.ts",
"src/ts/sandbox-redirect.ts"
]
},
"packageManager": "yarn@4.12.0"
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/ts/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'ace-builds/src-noconflict/theme-tomorrow';
import 'ace-builds/src-noconflict/theme-tomorrow_night';


/* eslint-disable no-unused-vars */
export enum EditorTheme {
Light = 'ace/theme/tomorrow',
Dark = 'ace/theme/tomorrow_night'
Expand All @@ -16,7 +15,6 @@ export enum EditorLanguage {
Ada = 'ace/mode/ada',
C_CPP = 'ace/mode/c_cpp'
}
/* eslint-enable no-unused-vars */

interface SessionData {
initialContents: string;
Expand Down
1 change: 0 additions & 1 deletion frontend/src/ts/sandbox-redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const cookies = new Cookies({
* Redirects the user to main learn site if not authenticated
*/
export function sandboxRedirect(): void {
/* istanbul ignore next */
const cookieName = "Learn_Sandbox_Authenticated";
const cookieValue = cookies.get(cookieName) as string;
const cookieReferenceValue = "true";
Expand Down
12 changes: 2 additions & 10 deletions frontend/src/ts/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,6 @@ class Widget {
return ret;
}

/**
* Construct the server address string
* @param {string} url - the url suffix
* @returns {string} - the full constructed url
*/
private serverAddress(url: string): string {
return this.server + '/' + url + '/';
}


/**
* Gets default compiler switches set on widget.
Expand Down Expand Up @@ -400,7 +391,8 @@ class Widget {
d.appendChild(document.createElement('br'));
const helpDiv = document.createElement('div');
helpDiv.classList.add('compiler-switch-help-info-click-remove');
helpDiv.textContent = '(' + Strings.COMPILER_SWITCH_REMOVE_HELP_MESSAGE + ')';
helpDiv.textContent =
'(' + Strings.COMPILER_SWITCH_REMOVE_HELP_MESSAGE + ')';
d.appendChild(helpDiv);
d.classList.remove('disabled');
});
Expand Down
15 changes: 15 additions & 0 deletions frontend/tests/rst/code_block_info.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Code Block Info
===============

This page has a widget with code-block-info for test purposes.

.. code:: ada no_button project=Test.CodeBlockInfo

procedure Test is

begin

null;

end Test;

2 changes: 2 additions & 0 deletions frontend/tests/rst/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ This is the main html page for HTML test template generation

Single <single>
Lab <lab>
Multi <multi>
Code Block Info <code_block_info>
24 changes: 24 additions & 0 deletions frontend/tests/rst/multi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Multi
=====

This page has multiple widgets with the same project name for test purposes.

.. code:: ada run_button project=Test.Multi

procedure Test1 is

begin

null;

end Test1;

.. code:: ada run_button project=Test.Multi

procedure Test2 is

begin

null;

end Test2;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from run output!
87 changes: 87 additions & 0 deletions frontend/tests/ts/download.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import chaiAsPromised from 'chai-as-promised';

const chai = use(chaiDom);

import JSZip from 'jszip';
import FileSaver from 'file-saver';

import {
getLanguages,
getUnparsedSwitches,
parseSwitches,
findMains,
getMain,
getGprContents,
downloadProject,
UnparsedSwitches,
} from '../../src/ts/download';
import {ResourceList} from '../../src/ts/resource';

Expand Down Expand Up @@ -70,6 +76,28 @@ describe('Download', () => {
});
});

describe('#getUnparsedSwitches()', () => {
it('should parse valid switches JSON', () => {
const raw = JSON.stringify({Builder: ['-g'], Compiler: ['-O2']});
const result = getUnparsedSwitches(raw);
expect(result.Builder).to.deep.equal(['-g']);
expect(result.Compiler).to.deep.equal(['-O2']);
});

it('should return empty arrays for missing keys', () => {
const raw = JSON.stringify({});
const result = getUnparsedSwitches(raw);
expect(result.Builder).to.deep.equal([]);
expect(result.Compiler).to.deep.equal([]);
});

it('should throw on invalid JSON', () => {
expect(() => getUnparsedSwitches('not json')).to.throw(
'Failed to parse switches JSON: not json'
);
});
});

describe('#parseSwitches()', () => {
it('should find no switches', () => {
const parsedSwitches = parseSwitches({Builder: [], Compiler: []});
Expand Down Expand Up @@ -245,4 +273,63 @@ describe('Download', () => {
expect(gpr_spark).to.not.contain('--');
});
});

describe('#downloadProject()', () => {
let capturedFileNames: string[] = [];
let savedFilename: string | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let origGenerateAsync: any;

const files: ResourceList = [
{basename: 'main.adb', contents: 'procedure Main is begin null; end Main;'},
];
const switches: UnparsedSwitches = {Builder: [], Compiler: []};

before(() => {
origGenerateAsync = JSZip.prototype.generateAsync;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(JSZip.prototype as any).generateAsync = function(options: any) {
capturedFileNames = Object.keys((this as JSZip).files);
return origGenerateAsync.call(this, options);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(FileSaver as any).saveAs = (_blob: unknown, name: string): void => {
savedFilename = name;
};
});

after(() => {
(JSZip.prototype as any).generateAsync = origGenerateAsync;
});

beforeEach(() => {
capturedFileNames = [];
savedFilename = undefined;
});

it('should name the zip after the project', async () => {
downloadProject(files, switches, '', 'MyProject', false);
await new Promise((r) => setTimeout(r, 200));
expect(savedFilename).to.equal('MyProject.zip');
});

it('should include source, main.gpr, and main.adc in non-SPARK mode', async () => {
downloadProject(files, switches, 'main.adb', 'Test', false);
await new Promise((r) => setTimeout(r, 200));
expect(capturedFileNames).to.include('main.adb');
expect(capturedFileNames).to.include('main.gpr');
expect(capturedFileNames).to.include('main.adc');
expect(capturedFileNames).not.to.include('main_spark.gpr');
expect(capturedFileNames).not.to.include('main_spark.adc');
});

it('should also include main_spark.gpr and main_spark.adc in SPARK mode', async () => {
downloadProject(files, switches, 'main.adb', 'Test', true);
await new Promise((r) => setTimeout(r, 200));
expect(capturedFileNames).to.include('main.gpr');
expect(capturedFileNames).to.include('main.adc');
expect(capturedFileNames).to.include('main_spark.gpr');
expect(capturedFileNames).to.include('main_spark.adc');
});
});
});
21 changes: 21 additions & 0 deletions frontend/tests/ts/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ describe('Editor', () => {
const session = editor.getSession();
expect(session.getValue()).to.equal(resource.contents);
});

it('should set C_CPP mode for a .c file', () => {
const cResource: Resource = {basename: 'main.c', contents: 'int main() { return 0; }'};
inTest.addSession(cResource.basename, cResource.contents);
inTest.setSession(cResource.basename);
const session = editor.getSession();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((session.getMode() as any).$id).to.equal('ace/mode/c_cpp');
inTest.setSession(resource.basename);
});
});

describe('#addNonTabbedEditor(), #refresh()', () => {
it('should resize non-tabbed editors when refresh(false) is called', () => {
const elem = document.createElement('div');
document.body.appendChild(elem);
inTest.addNonTabbedEditor(resource.basename, elem);
// Should not throw
expect(() => inTest.refresh(false)).not.to.throw();
document.body.removeChild(elem);
});
});

describe('#getSessionContent()', () => {
Expand Down
6 changes: 4 additions & 2 deletions frontend/tests/ts/server.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Import testing libs
import { expect, use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiDom from 'chai-dom';

use(chaiAsPromised);
const chai = use(chaiDom);

import {Server, WebSocket} from 'mock-socket';
Expand Down Expand Up @@ -104,7 +106,7 @@ describe('ServerWorker', () => {
});

it('should throw an exception when AWS rejects the request', async () => {
expect(client.execute(tsData, 2000)).to.be.rejectedWith(expectedErrorMsg);
await expect(client.execute(tsData, 2000)).to.be.rejectedWith(expectedErrorMsg);
});
});

Expand All @@ -124,7 +126,7 @@ describe('ServerWorker', () => {
});

it('should timeout if no response is recieved', async () => {
expect(client.execute(tsData, timeout)).to.be.rejectedWith(expectedErrorMsg);
await expect(client.execute(tsData, timeout)).to.be.rejectedWith(expectedErrorMsg);
});
});
});
Loading
Loading