mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-22 11:45:28 -05:00
Apply prettier to css, html, js, md, ts, and yml (#1249)
* Apply prettier to css, js, html, md, ts, and yml As a followup I will add prettier to the .pre-commit config. This patch is 100% generated by prettier. I used a forked version of prettier that understands the py-script tag. See https://github.com/hoodmane/pyscript-prettier-precommit for more info. * Apply old pre-commit * Revert some problems * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Revert some changes * More changes * Fix pre-commit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
module.exports = "";
|
||||
module.exports = '';
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* nothing.
|
||||
*/
|
||||
|
||||
console.warn(`.py files that are not explicitly mocked in \
|
||||
console.warn(`.py files that are not explicitly mocked in \
|
||||
jest.config.js and /__mocks__/ are mocked as empty strings`);
|
||||
|
||||
module.exports = "";
|
||||
module.exports = '';
|
||||
|
||||
@@ -17,7 +17,7 @@ module.exports = {
|
||||
url: 'http://localhost',
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^.*?pyscript\.py$': '<rootDir>/__mocks__/_pyscript.js',
|
||||
'^.*?pyscript.py$': '<rootDir>/__mocks__/_pyscript.js',
|
||||
'^[./a-zA-Z0-9$_-]+\\.py$': '<rootDir>/__mocks__/fileMock.js',
|
||||
'\\.(css)$': '<rootDir>/__mocks__/cssMock.js',
|
||||
},
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
{
|
||||
"name": "pyscript",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"build-min": "NODE_ENV=production rollup -c",
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"tsc": "tsc --noEmit",
|
||||
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
|
||||
"format": "prettier --write './src/**/*.{js,html,ts}'",
|
||||
"lint": "eslint './src/**/*.{js,html,ts}'",
|
||||
"lint:fix": "eslint --fix './src/**/*.{js,html,ts}'",
|
||||
"xprelint": "npm run format",
|
||||
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
||||
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.1.2",
|
||||
"@rollup/plugin-commonjs": "22.0.2",
|
||||
"@rollup/plugin-legacy": "2.2.0",
|
||||
"@rollup/plugin-node-resolve": "14.1.0",
|
||||
"@rollup/plugin-typescript": "8.5.0",
|
||||
"@types/codemirror": "^5.60.5",
|
||||
"@types/jest": "29.1.2",
|
||||
"@types/node": "18.8.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.39.0",
|
||||
"@typescript-eslint/parser": "5.39.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.25.0",
|
||||
"jest": "29.1.2",
|
||||
"jest-environment-jsdom": "29.1.2",
|
||||
"prettier": "2.7.1",
|
||||
"pyodide": "0.22.1",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-copy": "3.4.0",
|
||||
"rollup-plugin-css-only": "3.1.0",
|
||||
"rollup-plugin-livereload": "2.0.5",
|
||||
"rollup-plugin-serve": "2.0.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"ts-jest": "29.0.3",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "6.1.1",
|
||||
"@codemirror/lang-python": "6.0.2",
|
||||
"@codemirror/language": "6.2.1",
|
||||
"@codemirror/state": "6.1.2",
|
||||
"@codemirror/theme-one-dark": "6.1.0",
|
||||
"@codemirror/view": "6.3.0",
|
||||
"codemirror": "6.0.1"
|
||||
}
|
||||
"name": "pyscript",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"build-min": "NODE_ENV=production rollup -c",
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"tsc": "tsc --noEmit",
|
||||
"format:check": "prettier --check './src/**/*.{js,html,ts}'",
|
||||
"format": "prettier --write './src/**/*.{js,html,ts}'",
|
||||
"lint": "eslint './src/**/*.{js,html,ts}'",
|
||||
"lint:fix": "eslint --fix './src/**/*.{js,html,ts}'",
|
||||
"xprelint": "npm run format",
|
||||
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
||||
"test:watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.1.2",
|
||||
"@rollup/plugin-commonjs": "22.0.2",
|
||||
"@rollup/plugin-legacy": "2.2.0",
|
||||
"@rollup/plugin-node-resolve": "14.1.0",
|
||||
"@rollup/plugin-typescript": "8.5.0",
|
||||
"@types/codemirror": "^5.60.5",
|
||||
"@types/jest": "29.1.2",
|
||||
"@types/node": "18.8.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.39.0",
|
||||
"@typescript-eslint/parser": "5.39.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.25.0",
|
||||
"jest": "29.1.2",
|
||||
"jest-environment-jsdom": "29.1.2",
|
||||
"prettier": "2.7.1",
|
||||
"pyodide": "0.22.1",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-copy": "3.4.0",
|
||||
"rollup-plugin-css-only": "3.1.0",
|
||||
"rollup-plugin-livereload": "2.0.5",
|
||||
"rollup-plugin-serve": "2.0.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"ts-jest": "29.0.3",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "6.1.1",
|
||||
"@codemirror/lang-python": "6.0.2",
|
||||
"@codemirror/language": "6.2.1",
|
||||
"@codemirror/state": "6.1.2",
|
||||
"@codemirror/theme-one-dark": "6.1.0",
|
||||
"@codemirror/view": "6.3.0",
|
||||
"codemirror": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css" />
|
||||
<link rel="stylesheet" href="pyscript.css" />
|
||||
<script defer src="pyscript.min.js"></script>
|
||||
<title>PyScript</title>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://unpkg.com/mvp.css@1.12/mvp.css">
|
||||
<link rel="stylesheet" href="pyscript.css">
|
||||
<script defer src="pyscript.min.js"></script>
|
||||
<title>PyScript</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1><py-script></h1>
|
||||
<ul>
|
||||
<li><a href="pyscript.js">pyscript.js</a></li>
|
||||
<li><a href="pyscript.min.js">pyscript.min.js</a></li>
|
||||
<li><a href="pyscript.css">pyscript.css</a></li>
|
||||
<li><a href="pyscript.min.js.map">pyscript.min.js.map</a></li>
|
||||
<li><a href="pyscript.js.map">pyscript.js.map</a></li>
|
||||
</ul>
|
||||
<div id="out"></div>
|
||||
<py-script std-out="out">
|
||||
import sys
|
||||
print(sys.version)
|
||||
</py-script>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1><py-script></h1>
|
||||
<ul>
|
||||
<li><a href="pyscript.js">pyscript.js</a></li>
|
||||
<li><a href="pyscript.min.js">pyscript.min.js</a></li>
|
||||
<li><a href="pyscript.css">pyscript.css</a></li>
|
||||
<li><a href="pyscript.min.js.map">pyscript.min.js.map</a></li>
|
||||
<li><a href="pyscript.js.map">pyscript.js.map</a></li>
|
||||
</ul>
|
||||
<div id="out"></div>
|
||||
<py-script std-out="out">
|
||||
import sys
|
||||
print(sys.version)
|
||||
</py-script>
|
||||
|
||||
<h2>Example</h2>
|
||||
<pre style="padding:1em;border:1px solid #000000;"><!DOCTYPE html>
|
||||
<h2>Example</h2>
|
||||
<pre style="padding: 1em; border: 1px solid #000000">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -47,8 +47,8 @@ now = datetime.now()
|
||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||
</py-script>
|
||||
</body>
|
||||
</html></pre>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html></pre
|
||||
>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,69 +1,70 @@
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import legacy from '@rollup/plugin-legacy';
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import css from "rollup-plugin-css-only";
|
||||
import serve from "rollup-plugin-serve";
|
||||
import { string } from "rollup-plugin-string";
|
||||
import copy from 'rollup-plugin-copy'
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import css from 'rollup-plugin-css-only';
|
||||
import serve from 'rollup-plugin-serve';
|
||||
import { string } from 'rollup-plugin-string';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
|
||||
const production = !process.env.ROLLUP_WATCH || process.env.NODE_ENV === 'production';
|
||||
|
||||
const copy_targets = {
|
||||
targets: [
|
||||
{ src: 'public/index.html', dest: 'build' },
|
||||
{ src: 'src/plugins/*', dest: 'build/plugins' }
|
||||
]
|
||||
}
|
||||
targets: [
|
||||
{ src: 'public/index.html', dest: 'build' },
|
||||
{ src: 'src/plugins/*', dest: 'build/plugins' },
|
||||
],
|
||||
};
|
||||
|
||||
if( !production ){
|
||||
copy_targets.targets.push({ src: 'build/*', dest: 'examples/build' })
|
||||
if (!production) {
|
||||
copy_targets.targets.push({ src: 'build/*', dest: 'examples/build' });
|
||||
}
|
||||
|
||||
export default {
|
||||
input: "src/main.ts",
|
||||
output:[
|
||||
{
|
||||
file: "build/pyscript.js",
|
||||
format: "iife",
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
name: "pyscript",
|
||||
input: 'src/main.ts',
|
||||
output: [
|
||||
{
|
||||
file: 'build/pyscript.js',
|
||||
format: 'iife',
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
name: 'pyscript',
|
||||
},
|
||||
{
|
||||
file: 'build/pyscript.min.js',
|
||||
format: 'iife',
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
name: 'pyscript',
|
||||
plugins: [terser()],
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
css({ output: 'pyscript.css' }),
|
||||
// Bundle all the Python files into the output file
|
||||
string({
|
||||
include: './src/**/*.py',
|
||||
}),
|
||||
legacy({ 'src/toml.js': 'toml' }),
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
sourceMap: !production,
|
||||
inlineSources: !production,
|
||||
}),
|
||||
// This will make sure that examples will always get the latest build folder
|
||||
copy(copy_targets),
|
||||
// production && terser(),
|
||||
!production &&
|
||||
serve({
|
||||
port: 8080,
|
||||
contentBase: 'examples',
|
||||
}),
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
{
|
||||
file: "build/pyscript.min.js",
|
||||
format: "iife",
|
||||
sourcemap: true,
|
||||
inlineDynamicImports: true,
|
||||
name: "pyscript",
|
||||
plugins: [terser()],
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
css({ output: "pyscript.css" }),
|
||||
// Bundle all the Python files into the output file
|
||||
string({
|
||||
include: "./src/**/*.py",
|
||||
}),
|
||||
legacy({ 'src/toml.js': 'toml' }),
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
sourceMap: !production,
|
||||
inlineSources: !production,
|
||||
}),
|
||||
// This will make sure that examples will always get the latest build folder
|
||||
copy(copy_targets),
|
||||
// production && terser(),
|
||||
!production && serve({
|
||||
port: 8080,
|
||||
contentBase: 'examples'}
|
||||
)
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -34,11 +34,16 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
|
||||
const pySrc = await this.getPySrc();
|
||||
this.innerHTML = '';
|
||||
|
||||
app.plugins.beforePyScriptExec({interpreter: interpreter, src: pySrc, pyScriptTag: this});
|
||||
app.plugins.beforePyScriptExec({ interpreter: interpreter, src: pySrc, pyScriptTag: this });
|
||||
const result = (await pyExec(interpreter, pySrc, this)).result;
|
||||
app.plugins.afterPyScriptExec({interpreter: interpreter, src: pySrc, pyScriptTag: this, result: result});
|
||||
app.plugins.afterPyScriptExec({
|
||||
interpreter: interpreter,
|
||||
src: pySrc,
|
||||
pyScriptTag: this,
|
||||
result: result,
|
||||
});
|
||||
} finally {
|
||||
releaseLock()
|
||||
releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +204,7 @@ function createElementsWithEventListeners(interpreter: InterpreterClient, pyAttr
|
||||
void (async () => {
|
||||
try {
|
||||
await interpreter.run(handlerCode);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
displayPyException(err, el.parentElement);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -12,51 +12,49 @@ The convention is:
|
||||
*/
|
||||
|
||||
export enum ErrorCode {
|
||||
GENERIC = "PY0000", // Use this only for development then change to a more specific error code
|
||||
FETCH_ERROR = "PY0001",
|
||||
FETCH_NAME_ERROR = "PY0002",
|
||||
// Currently these are created depending on error code received from fetching
|
||||
FETCH_UNAUTHORIZED_ERROR = "PY0401",
|
||||
FETCH_FORBIDDEN_ERROR = "PY0403",
|
||||
FETCH_NOT_FOUND_ERROR = "PY0404",
|
||||
FETCH_SERVER_ERROR = "PY0500",
|
||||
FETCH_UNAVAILABLE_ERROR = "PY0503",
|
||||
BAD_CONFIG = "PY1000",
|
||||
MICROPIP_INSTALL_ERROR = "PY1001",
|
||||
BAD_PLUGIN_FILE_EXTENSION = "PY2000",
|
||||
NO_DEFAULT_EXPORT = "PY2001",
|
||||
TOP_LEVEL_AWAIT = "PY9000"
|
||||
GENERIC = 'PY0000', // Use this only for development then change to a more specific error code
|
||||
FETCH_ERROR = 'PY0001',
|
||||
FETCH_NAME_ERROR = 'PY0002',
|
||||
// Currently these are created depending on error code received from fetching
|
||||
FETCH_UNAUTHORIZED_ERROR = 'PY0401',
|
||||
FETCH_FORBIDDEN_ERROR = 'PY0403',
|
||||
FETCH_NOT_FOUND_ERROR = 'PY0404',
|
||||
FETCH_SERVER_ERROR = 'PY0500',
|
||||
FETCH_UNAVAILABLE_ERROR = 'PY0503',
|
||||
BAD_CONFIG = 'PY1000',
|
||||
MICROPIP_INSTALL_ERROR = 'PY1001',
|
||||
BAD_PLUGIN_FILE_EXTENSION = 'PY2000',
|
||||
NO_DEFAULT_EXPORT = 'PY2001',
|
||||
TOP_LEVEL_AWAIT = 'PY9000',
|
||||
}
|
||||
|
||||
export class UserError extends Error {
|
||||
messageType: MessageType;
|
||||
errorCode: ErrorCode;
|
||||
messageType: MessageType;
|
||||
errorCode: ErrorCode;
|
||||
|
||||
constructor(errorCode: ErrorCode, message: string, t: MessageType = "text") {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.name = "UserError";
|
||||
this.messageType = t;
|
||||
this.message = `(${errorCode}): ${message}`;
|
||||
}
|
||||
constructor(errorCode: ErrorCode, message: string, t: MessageType = 'text') {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
this.name = 'UserError';
|
||||
this.messageType = t;
|
||||
this.message = `(${errorCode}): ${message}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class FetchError extends UserError {
|
||||
errorCode: ErrorCode;
|
||||
constructor(errorCode: ErrorCode, message: string) {
|
||||
super(errorCode, message)
|
||||
this.name = "FetchError";
|
||||
}
|
||||
errorCode: ErrorCode;
|
||||
constructor(errorCode: ErrorCode, message: string) {
|
||||
super(errorCode, message);
|
||||
this.name = 'FetchError';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class InstallError extends UserError {
|
||||
errorCode: ErrorCode;
|
||||
constructor(errorCode: ErrorCode, message: string) {
|
||||
super(errorCode, message)
|
||||
this.name = "InstallError";
|
||||
}
|
||||
errorCode: ErrorCode;
|
||||
constructor(errorCode: ErrorCode, message: string) {
|
||||
super(errorCode, message);
|
||||
this.name = 'InstallError';
|
||||
}
|
||||
}
|
||||
|
||||
export function _createAlertBanner(
|
||||
|
||||
@@ -11,7 +11,6 @@ InterpreterClient class is responsible to request code execution
|
||||
(among other things) from a `RemoteInterpreter`
|
||||
*/
|
||||
export class InterpreterClient extends Object {
|
||||
|
||||
_remote: RemoteInterpreter;
|
||||
config: AppConfig;
|
||||
/**
|
||||
@@ -41,7 +40,7 @@ export class InterpreterClient extends Object {
|
||||
* the remote interpreter.
|
||||
* Python exceptions are turned into JS exceptions.
|
||||
* */
|
||||
async run(code: string): Promise<{result: any}> {
|
||||
async run(code: string): Promise<{ result: any }> {
|
||||
return await this._remote.run(code);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ function getLogger(prefix: string): Logger {
|
||||
}
|
||||
|
||||
function _makeLogger(prefix: string): Logger {
|
||||
prefix =`[${prefix}] `;
|
||||
prefix = `[${prefix}] `;
|
||||
|
||||
function make(level: string) {
|
||||
const out_fn = console[level].bind(console);
|
||||
|
||||
@@ -208,7 +208,7 @@ export class PyScriptApp {
|
||||
logger.info('importing pyscript');
|
||||
|
||||
// Save and load pyscript.py from FS
|
||||
interpreter._remote.interface.FS.mkdirTree("/home/pyodide/pyscript");
|
||||
interpreter._remote.interface.FS.mkdirTree('/home/pyodide/pyscript');
|
||||
interpreter._remote.interface.FS.writeFile('pyscript/__init__.py', pyscript);
|
||||
//Refresh the module cache so Python consistently finds pyscript module
|
||||
interpreter._remote.invalidate_module_path_cache();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { make_PyScript } from './components/pyscript';
|
||||
import { InterpreterClient } from './interpreter_client';
|
||||
|
||||
const logger = getLogger('plugin');
|
||||
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
|
||||
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
|
||||
|
||||
export class Plugin {
|
||||
/** Validate the configuration of the plugin and handle default values.
|
||||
@@ -48,7 +48,7 @@ export class Plugin {
|
||||
* @param options.src {string} The Python source code to be evaluated
|
||||
* @param options.pyScriptTag The <py-script> HTML tag that originated the evaluation
|
||||
*/
|
||||
beforePyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag}) {}
|
||||
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }) {}
|
||||
|
||||
/** The Python in a <py-script> has just been evaluated, but control
|
||||
* has not been ceded back to the JavaScript event loop yet
|
||||
@@ -58,7 +58,12 @@ export class Plugin {
|
||||
* @param options.pyScriptTag The <py-script> HTML tag that originated the evaluation
|
||||
* @param options.result The returned result of evaluating the Python (if any)
|
||||
*/
|
||||
afterPyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag, result: any}) {}
|
||||
afterPyScriptExec(options: {
|
||||
interpreter: InterpreterClient;
|
||||
src: string;
|
||||
pyScriptTag: PyScriptTag;
|
||||
result: any;
|
||||
}) {}
|
||||
|
||||
/** Startup complete. The interpreter is initialized and ready, user
|
||||
* scripts have been executed: the main initialization logic ends here and
|
||||
@@ -122,13 +127,13 @@ export class PluginManager {
|
||||
for (const p of this._pythonPlugins) p.afterStartup?.(interpreter);
|
||||
}
|
||||
|
||||
beforePyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag}) {
|
||||
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }) {
|
||||
for (const p of this._plugins) p.beforePyScriptExec?.(options);
|
||||
|
||||
for (const p of this._pythonPlugins) p.beforePyScriptExec?.callKwargs(options);
|
||||
}
|
||||
|
||||
afterPyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag, result: any}) {
|
||||
afterPyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag; result: any }) {
|
||||
for (const p of this._plugins) p.afterPyScriptExec?.(options);
|
||||
|
||||
for (const p of this._pythonPlugins) p.afterPyScriptExec?.callKwargs(options);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { joinPaths } from '../utils';
|
||||
import { FetchConfig } from "../pyconfig";
|
||||
import { FetchConfig } from '../pyconfig';
|
||||
import { UserError, ErrorCode } from '../exceptions';
|
||||
|
||||
export function calculatePaths(fetch_cfg: FetchConfig[]) {
|
||||
@@ -10,14 +10,9 @@ export function calculatePaths(fetch_cfg: FetchConfig[]) {
|
||||
const to_folder = each_fetch_cfg.to_folder || '.';
|
||||
const to_file = each_fetch_cfg.to_file;
|
||||
const files = each_fetch_cfg.files;
|
||||
if (files !== undefined)
|
||||
{
|
||||
if (to_file !== undefined)
|
||||
{
|
||||
throw new UserError(
|
||||
ErrorCode.BAD_CONFIG,
|
||||
`Cannot use 'to_file' and 'files' parameters together!`
|
||||
);
|
||||
if (files !== undefined) {
|
||||
if (to_file !== undefined) {
|
||||
throw new UserError(ErrorCode.BAD_CONFIG, `Cannot use 'to_file' and 'files' parameters together!`);
|
||||
}
|
||||
for (const each_f of files) {
|
||||
const each_fetch_path = joinPaths([from, each_f]);
|
||||
@@ -31,10 +26,9 @@ export function calculatePaths(fetch_cfg: FetchConfig[]) {
|
||||
if (filename === '') {
|
||||
throw new UserError(
|
||||
ErrorCode.BAD_CONFIG,
|
||||
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`
|
||||
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`,
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
paths.push(joinPaths([to_folder, filename]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Plugin } from "../plugin";
|
||||
import { TargetedStdio, StdioMultiplexer } from "../stdio";
|
||||
import { make_PyScript } from "../components/pyscript";
|
||||
import { InterpreterClient } from "../interpreter_client";
|
||||
import { Plugin } from '../plugin';
|
||||
import { TargetedStdio, StdioMultiplexer } from '../stdio';
|
||||
import { make_PyScript } from '../components/pyscript';
|
||||
import { InterpreterClient } from '../interpreter_client';
|
||||
|
||||
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
|
||||
type PyScriptTag = InstanceType<ReturnType<typeof make_PyScript>>;
|
||||
|
||||
/**
|
||||
* The StdioDirector plugin captures the output to Python's sys.stdio and
|
||||
@@ -17,8 +17,8 @@ export class StdioDirector extends Plugin {
|
||||
_stdioMultiplexer: StdioMultiplexer;
|
||||
|
||||
constructor(stdio: StdioMultiplexer) {
|
||||
super()
|
||||
this._stdioMultiplexer = stdio
|
||||
super();
|
||||
this._stdioMultiplexer = stdio;
|
||||
}
|
||||
|
||||
/** Prior to a <py-script> tag being evaluated, if that tag itself has
|
||||
@@ -27,30 +27,35 @@ export class StdioDirector extends Plugin {
|
||||
* with that ID for the duration of the evaluation.
|
||||
*
|
||||
*/
|
||||
beforePyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag}): void {
|
||||
if (options.pyScriptTag.hasAttribute("output")){
|
||||
const targeted_io = new TargetedStdio(options.pyScriptTag, "output", true, true)
|
||||
options.pyScriptTag.stdout_manager = targeted_io
|
||||
this._stdioMultiplexer.addListener(targeted_io)
|
||||
beforePyScriptExec(options: { interpreter: InterpreterClient; src: string; pyScriptTag: PyScriptTag }): void {
|
||||
if (options.pyScriptTag.hasAttribute('output')) {
|
||||
const targeted_io = new TargetedStdio(options.pyScriptTag, 'output', true, true);
|
||||
options.pyScriptTag.stdout_manager = targeted_io;
|
||||
this._stdioMultiplexer.addListener(targeted_io);
|
||||
}
|
||||
if (options.pyScriptTag.hasAttribute("stderr")){
|
||||
const targeted_io = new TargetedStdio(options.pyScriptTag, "stderr", false, true)
|
||||
options.pyScriptTag.stderr_manager = targeted_io
|
||||
this._stdioMultiplexer.addListener(targeted_io)
|
||||
if (options.pyScriptTag.hasAttribute('stderr')) {
|
||||
const targeted_io = new TargetedStdio(options.pyScriptTag, 'stderr', false, true);
|
||||
options.pyScriptTag.stderr_manager = targeted_io;
|
||||
this._stdioMultiplexer.addListener(targeted_io);
|
||||
}
|
||||
}
|
||||
|
||||
/** After a <py-script> tag is evaluated, if that tag has a 'stdout_manager'
|
||||
* (presumably TargetedStdio, or some other future IO handler), it is removed.
|
||||
*/
|
||||
afterPyScriptExec(options: {interpreter: InterpreterClient, src: string, pyScriptTag: PyScriptTag, result: any}): void {
|
||||
if (options.pyScriptTag.stdout_manager != null){
|
||||
this._stdioMultiplexer.removeListener(options.pyScriptTag.stdout_manager)
|
||||
options.pyScriptTag.stdout_manager = null
|
||||
afterPyScriptExec(options: {
|
||||
interpreter: InterpreterClient;
|
||||
src: string;
|
||||
pyScriptTag: PyScriptTag;
|
||||
result: any;
|
||||
}): void {
|
||||
if (options.pyScriptTag.stdout_manager != null) {
|
||||
this._stdioMultiplexer.removeListener(options.pyScriptTag.stdout_manager);
|
||||
options.pyScriptTag.stdout_manager = null;
|
||||
}
|
||||
if (options.pyScriptTag.stderr_manager != null){
|
||||
this._stdioMultiplexer.removeListener(options.pyScriptTag.stderr_manager)
|
||||
options.pyScriptTag.stderr_manager = null
|
||||
if (options.pyScriptTag.stderr_manager != null) {
|
||||
this._stdioMultiplexer.removeListener(options.pyScriptTag.stderr_manager);
|
||||
options.pyScriptTag.stderr_manager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ export async function pyExec(interpreter: InterpreterClient, pysrc: string, outE
|
||||
'\nSee https://docs.pyscript.net/latest/guides/asyncio.html for more information.',
|
||||
);
|
||||
}
|
||||
return (await interpreter.run(pysrc));
|
||||
return await interpreter.run(pysrc);
|
||||
} catch (err) {
|
||||
// XXX: currently we display exceptions in the same position as
|
||||
// the output. But we probably need a better way to do that,
|
||||
// e.g. allowing plugins to intercept exceptions and display them
|
||||
// in a configurable way.
|
||||
displayPyException(err, outElem);
|
||||
return {result: undefined};
|
||||
return { result: undefined };
|
||||
}
|
||||
} finally {
|
||||
pyscript_py.set_current_display_target(undefined);
|
||||
|
||||
@@ -37,9 +37,7 @@ export class RemoteInterpreter extends Object {
|
||||
// TODO: Remove this once `runtimes` is removed!
|
||||
interpreter: InterpreterInterface;
|
||||
|
||||
constructor(
|
||||
src = 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js'
|
||||
) {
|
||||
constructor(src = 'https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js') {
|
||||
super();
|
||||
this.src = src;
|
||||
}
|
||||
@@ -87,11 +85,11 @@ export class RemoteInterpreter extends Object {
|
||||
await this.loadPackage('micropip');
|
||||
}
|
||||
logger.info('pyodide loaded and initialized');
|
||||
await this.run('print("Python initialization complete")')
|
||||
await this.run('print("Python initialization complete")');
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
async run(code: string): Promise<{result: any}> {
|
||||
async run(code: string): Promise<{ result: any }> {
|
||||
/**
|
||||
* eslint wants `await` keyword to be used i.e.
|
||||
* { result: await this.interface.runPython(code) }
|
||||
@@ -134,10 +132,12 @@ export class RemoteInterpreter extends Object {
|
||||
// for which the signature of `loadPackage` accepts the above params as args i.e.
|
||||
// the call uses `logger.info.bind(logger), logger.info.bind(logger)`.
|
||||
const pyodide_version = (await this.run("import sys; sys.modules['pyodide'].__version__")).result.toString();
|
||||
if (pyodide_version.startsWith("0.22")) {
|
||||
await this.interface.loadPackage(names, { messageCallback: logger.info.bind(logger), errorCallback: logger.info.bind(logger) });
|
||||
}
|
||||
else {
|
||||
if (pyodide_version.startsWith('0.22')) {
|
||||
await this.interface.loadPackage(names, {
|
||||
messageCallback: logger.info.bind(logger),
|
||||
errorCallback: logger.info.bind(logger),
|
||||
});
|
||||
} else {
|
||||
await this.interface.loadPackage(names, logger.info.bind(logger), logger.info.bind(logger));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSingularWarning, escape } from "./utils";
|
||||
import { createSingularWarning, escape } from './utils';
|
||||
|
||||
export interface Stdio {
|
||||
stdout_writeline: (msg: string) => void;
|
||||
@@ -42,8 +42,7 @@ export class CaptureStdio implements Stdio {
|
||||
* specified by ID. Used with "output" keyword.
|
||||
*
|
||||
*/
|
||||
export class TargetedStdio implements Stdio{
|
||||
|
||||
export class TargetedStdio implements Stdio {
|
||||
source_element: HTMLElement;
|
||||
source_attribute: string;
|
||||
capture_stdout: boolean;
|
||||
@@ -66,31 +65,32 @@ export class TargetedStdio implements Stdio{
|
||||
*
|
||||
* @param msg The output to be written
|
||||
*/
|
||||
writeline_by_attribute(msg:string){
|
||||
const target_id = this.source_element.getAttribute(this.source_attribute)
|
||||
const target = document.getElementById(target_id)
|
||||
if (target === null) { // No matching ID
|
||||
createSingularWarning(`${this.source_attribute} = "${target_id}" does not match the id of any element on the page.`)
|
||||
writeline_by_attribute(msg: string) {
|
||||
const target_id = this.source_element.getAttribute(this.source_attribute);
|
||||
const target = document.getElementById(target_id);
|
||||
if (target === null) {
|
||||
// No matching ID
|
||||
createSingularWarning(
|
||||
`${this.source_attribute} = "${target_id}" does not match the id of any element on the page.`,
|
||||
);
|
||||
} else {
|
||||
msg = escape(msg).replace('\n', '<br>');
|
||||
if (!msg.endsWith('<br/>') && !msg.endsWith('<br>')) {
|
||||
msg = msg + '<br>';
|
||||
}
|
||||
else {
|
||||
msg = escape(msg).replace("\n", "<br>")
|
||||
if (!msg.endsWith("<br/>") && !msg.endsWith("<br>")){
|
||||
msg = msg + "<br>"
|
||||
}
|
||||
target.innerHTML += msg
|
||||
}
|
||||
}
|
||||
|
||||
stdout_writeline (msg: string) {
|
||||
if (this.capture_stdout){
|
||||
this.writeline_by_attribute(msg)
|
||||
target.innerHTML += msg;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stderr_writeline (msg: string) {
|
||||
if (this.capture_stderr){
|
||||
this.writeline_by_attribute(msg)
|
||||
stdout_writeline(msg: string) {
|
||||
if (this.capture_stdout) {
|
||||
this.writeline_by_attribute(msg);
|
||||
}
|
||||
}
|
||||
|
||||
stderr_writeline(msg: string) {
|
||||
if (this.capture_stderr) {
|
||||
this.writeline_by_attribute(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,9 +109,9 @@ export class StdioMultiplexer implements Stdio {
|
||||
}
|
||||
|
||||
removeListener(obj: Stdio) {
|
||||
const index = this._listeners.indexOf(obj)
|
||||
if (index > -1){
|
||||
this._listeners.splice(index, 1)
|
||||
const index = this._listeners.indexOf(obj);
|
||||
if (index > -1) {
|
||||
this._listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
/* py-config - not a component */
|
||||
py-config {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
/* py-{el} - components not defined */
|
||||
py-script:not(:defined) {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-repl:not(:defined) {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-title:not(:defined) {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-inputbox:not(:defined) {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-button:not(:defined) {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-box:not(:defined) {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||
Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ html {
|
||||
/* Pop-up second layer end */
|
||||
.alert-banner {
|
||||
position: relative;
|
||||
padding: .5rem 1.5rem .5rem .5rem;
|
||||
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
@@ -118,8 +119,8 @@ html {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.py-error{
|
||||
background-color: #FFE9E8;
|
||||
.py-error {
|
||||
background-color: #ffe9e8;
|
||||
border: solid;
|
||||
border-color: #f0625f;
|
||||
color: #9d041c;
|
||||
@@ -132,24 +133,24 @@ html {
|
||||
color: #794700;
|
||||
}
|
||||
|
||||
.alert-banner.py-error>#alert-close-button {
|
||||
.alert-banner.py-error > #alert-close-button {
|
||||
color: #9d041c;
|
||||
}
|
||||
|
||||
.alert-banner.py-warning>#alert-close-button {
|
||||
color: #794700
|
||||
.alert-banner.py-warning > #alert-close-button {
|
||||
color: #794700;
|
||||
}
|
||||
|
||||
#alert-close-button {
|
||||
position: absolute;
|
||||
right: .5rem;
|
||||
top: .5rem;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.py-box{
|
||||
.py-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
@@ -168,10 +169,7 @@ border: none;
|
||||
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
position: relative;
|
||||
--tw-ring-inset: var(--tw-empty,
|
||||
/*!*/
|
||||
/*!*/
|
||||
);
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
@@ -183,7 +181,7 @@ border: none;
|
||||
box-sizing: border-box;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(209, 213, 219)
|
||||
border-color: rgb(209, 213, 219);
|
||||
}
|
||||
|
||||
.editor-box:hover button {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { _createAlertBanner } from "./exceptions"
|
||||
import { _createAlertBanner } from './exceptions';
|
||||
|
||||
export function addClasses(element: HTMLElement, classes: string[]) {
|
||||
for (const entry of classes) {
|
||||
@@ -127,11 +127,11 @@ export function createLock() {
|
||||
* @private
|
||||
*/
|
||||
async function acquireLock() {
|
||||
const old_lock = _lock;
|
||||
let releaseLock: () => void;
|
||||
_lock = new Promise((resolve) => (releaseLock = resolve));
|
||||
await old_lock;
|
||||
return releaseLock;
|
||||
const old_lock = _lock;
|
||||
let releaseLock: () => void;
|
||||
_lock = new Promise(resolve => (releaseLock = resolve));
|
||||
await old_lock;
|
||||
return releaseLock;
|
||||
}
|
||||
return acquireLock;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +1,123 @@
|
||||
import { expect, it, jest, describe, afterEach } from "@jest/globals"
|
||||
import { _createAlertBanner, UserError, FetchError, ErrorCode } from "../../src/exceptions"
|
||||
import { expect, it, jest, describe, afterEach } from '@jest/globals';
|
||||
import { _createAlertBanner, UserError, FetchError, ErrorCode } from '../../src/exceptions';
|
||||
|
||||
describe("Test _createAlertBanner", () => {
|
||||
afterEach(() => {
|
||||
// Ensure we always have a clean body
|
||||
document.body.innerHTML = `<div>Hello World</div>`;
|
||||
})
|
||||
describe('Test _createAlertBanner', () => {
|
||||
afterEach(() => {
|
||||
// Ensure we always have a clean body
|
||||
document.body.innerHTML = `<div>Hello World</div>`;
|
||||
});
|
||||
|
||||
it("error level shouldn't contain close button", async () => {
|
||||
_createAlertBanner('Something went wrong!', 'error');
|
||||
|
||||
it("error level shouldn't contain close button", async () => {
|
||||
_createAlertBanner("Something went wrong!", "error");
|
||||
const banner = document.getElementsByClassName('alert-banner');
|
||||
const closeButton = document.getElementById('alert-close-button');
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toBe('Something went wrong!');
|
||||
expect(closeButton).toBeNull();
|
||||
});
|
||||
|
||||
const banner = document.getElementsByClassName("alert-banner");
|
||||
const closeButton = document.getElementById("alert-close-button");
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toBe("Something went wrong!");
|
||||
expect(closeButton).toBeNull();
|
||||
})
|
||||
it('warning level should contain close button', async () => {
|
||||
_createAlertBanner('This is a warning', 'warning');
|
||||
|
||||
it("warning level should contain close button", async () => {
|
||||
_createAlertBanner("This is a warning", "warning");
|
||||
const banner = document.getElementsByClassName('alert-banner');
|
||||
const closeButton = document.getElementById('alert-close-button');
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toContain('This is a warning');
|
||||
expect(closeButton).not.toBeNull();
|
||||
});
|
||||
|
||||
const banner = document.getElementsByClassName("alert-banner");
|
||||
const closeButton = document.getElementById("alert-close-button");
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toContain("This is a warning");
|
||||
expect(closeButton).not.toBeNull();
|
||||
})
|
||||
it('error level banner should log to console', async () => {
|
||||
const logSpy = jest.spyOn(console, 'error');
|
||||
|
||||
it("error level banner should log to console", async () => {
|
||||
const logSpy = jest.spyOn(console, "error");
|
||||
_createAlertBanner('Something went wrong!');
|
||||
|
||||
_createAlertBanner("Something went wrong!");
|
||||
expect(logSpy).toHaveBeenCalledWith('Something went wrong!');
|
||||
});
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith("Something went wrong!");
|
||||
it('warning level banner should log to console', async () => {
|
||||
const logSpy = jest.spyOn(console, 'warn');
|
||||
|
||||
})
|
||||
_createAlertBanner('This warning', 'warning');
|
||||
|
||||
it("warning level banner should log to console", async () => {
|
||||
const logSpy = jest.spyOn(console, "warn");
|
||||
expect(logSpy).toHaveBeenCalledWith('This warning');
|
||||
});
|
||||
|
||||
_createAlertBanner("This warning", "warning");
|
||||
it('close button should remove element from page', async () => {
|
||||
let banner = document.getElementsByClassName('alert-banner');
|
||||
expect(banner.length).toBe(0);
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith("This warning");
|
||||
})
|
||||
_createAlertBanner('Warning!', 'warning');
|
||||
|
||||
it("close button should remove element from page", async () => {
|
||||
let banner = document.getElementsByClassName("alert-banner");
|
||||
expect(banner.length).toBe(0);
|
||||
// Just a sanity check
|
||||
banner = document.getElementsByClassName('alert-banner');
|
||||
expect(banner.length).toBe(1);
|
||||
|
||||
_createAlertBanner("Warning!", "warning");
|
||||
const closeButton = document.getElementById('alert-close-button');
|
||||
if (closeButton) {
|
||||
closeButton.click();
|
||||
// Confirm that clicking the close button, removes the element
|
||||
banner = document.getElementsByClassName('alert-banner');
|
||||
expect(banner.length).toBe(0);
|
||||
} else {
|
||||
fail('Unable to find close button on the page, but should exist');
|
||||
}
|
||||
});
|
||||
|
||||
// Just a sanity check
|
||||
banner = document.getElementsByClassName("alert-banner");
|
||||
expect(banner.length).toBe(1);
|
||||
it("toggling logging off on error alert shouldn't log to console", async () => {
|
||||
const errorLogSpy = jest.spyOn(console, 'error');
|
||||
|
||||
const closeButton = document.getElementById("alert-close-button");
|
||||
if(closeButton) {
|
||||
closeButton.click();
|
||||
// Confirm that clicking the close button, removes the element
|
||||
banner = document.getElementsByClassName("alert-banner");
|
||||
expect(banner.length).toBe(0);
|
||||
} else {
|
||||
fail("Unable to find close button on the page, but should exist");
|
||||
}
|
||||
_createAlertBanner('Test error', 'error', 'text', false);
|
||||
expect(errorLogSpy).not.toHaveBeenCalledWith('Test error');
|
||||
});
|
||||
|
||||
})
|
||||
it("toggling logging off on warning alert shouldn't log to console", async () => {
|
||||
const warnLogSpy = jest.spyOn(console, 'warn');
|
||||
_createAlertBanner('Test warning', 'warning', 'text', false);
|
||||
expect(warnLogSpy).not.toHaveBeenCalledWith('Test warning');
|
||||
});
|
||||
|
||||
it("toggling logging off on error alert shouldn't log to console", async () => {
|
||||
const errorLogSpy = jest.spyOn(console, "error");
|
||||
it('_createAlertbanner messageType text writes message to content', async () => {
|
||||
let banner = document.getElementsByClassName('alert-banner');
|
||||
expect(banner.length).toBe(0);
|
||||
|
||||
_createAlertBanner("Test error", "error", "text", false);
|
||||
expect(errorLogSpy).not.toHaveBeenCalledWith("Test error");
|
||||
})
|
||||
const message = '<p>Test message</p>';
|
||||
_createAlertBanner(message, 'error', 'text');
|
||||
banner = document.getElementsByClassName('alert-banner');
|
||||
|
||||
it("toggling logging off on warning alert shouldn't log to console", async () => {
|
||||
const warnLogSpy = jest.spyOn(console, "warn");
|
||||
_createAlertBanner("Test warning", "warning", "text", false);
|
||||
expect(warnLogSpy).not.toHaveBeenCalledWith("Test warning");
|
||||
})
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toBe('<p>Test message</p>');
|
||||
expect(banner[0].textContent).toBe(message);
|
||||
});
|
||||
|
||||
it('_createAlertbanner messageType html writes message to innerHTML', async () => {
|
||||
let banner = document.getElementsByClassName('alert-banner');
|
||||
expect(banner.length).toBe(0);
|
||||
|
||||
it('_createAlertbanner messageType text writes message to content', async () => {
|
||||
let banner = document.getElementsByClassName("alert-banner");
|
||||
expect(banner.length).toBe(0);
|
||||
const message = '<p>Test message</p>';
|
||||
_createAlertBanner(message, 'error', 'html');
|
||||
banner = document.getElementsByClassName('alert-banner');
|
||||
|
||||
const message = '<p>Test message</p>'
|
||||
_createAlertBanner(message, 'error', 'text');
|
||||
banner = document.getElementsByClassName("alert-banner");
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toBe(message);
|
||||
expect(banner[0].textContent).toBe('Test message');
|
||||
});
|
||||
});
|
||||
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toBe("<p>Test message</p>");
|
||||
expect(banner[0].textContent).toBe(message);
|
||||
})
|
||||
describe('Test Exceptions', () => {
|
||||
it('UserError contains errorCode and shows in message', async () => {
|
||||
const errorCode = ErrorCode.BAD_CONFIG;
|
||||
const message = 'Test error';
|
||||
const userError = new UserError(ErrorCode.BAD_CONFIG, message);
|
||||
expect(userError.errorCode).toBe(errorCode);
|
||||
expect(userError.message).toBe(`(${errorCode}): ${message}`);
|
||||
});
|
||||
|
||||
it('_createAlertbanner messageType html writes message to innerHTML', async () => {
|
||||
let banner = document.getElementsByClassName("alert-banner");
|
||||
expect(banner.length).toBe(0);
|
||||
|
||||
const message = '<p>Test message</p>';
|
||||
_createAlertBanner(message, 'error', 'html');
|
||||
banner = document.getElementsByClassName("alert-banner");
|
||||
|
||||
expect(banner.length).toBe(1);
|
||||
expect(banner[0].innerHTML).toBe(message);
|
||||
expect(banner[0].textContent).toBe("Test message");
|
||||
})
|
||||
})
|
||||
|
||||
describe("Test Exceptions", () => {
|
||||
it('UserError contains errorCode and shows in message', async() => {
|
||||
const errorCode = ErrorCode.BAD_CONFIG;
|
||||
const message = 'Test error';
|
||||
const userError = new UserError(ErrorCode.BAD_CONFIG, message);
|
||||
expect(userError.errorCode).toBe(errorCode);
|
||||
expect(userError.message).toBe(`(${errorCode}): ${message}`);
|
||||
})
|
||||
|
||||
it('FetchError contains errorCode and shows in message', async() => {
|
||||
const errorCode = ErrorCode.FETCH_NOT_FOUND_ERROR;
|
||||
const message = 'Test error';
|
||||
const fetchError = new FetchError(errorCode, message);
|
||||
expect(fetchError.errorCode).toBe(errorCode);
|
||||
expect(fetchError.message).toBe(`(${errorCode}): ${message}`);
|
||||
})
|
||||
})
|
||||
it('FetchError contains errorCode and shows in message', async () => {
|
||||
const errorCode = ErrorCode.FETCH_NOT_FOUND_ERROR;
|
||||
const message = 'Test error';
|
||||
const fetchError = new FetchError(errorCode, message);
|
||||
expect(fetchError.errorCode).toBe(errorCode);
|
||||
expect(fetchError.message).toBe(`(${errorCode}): ${message}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,111 +1,107 @@
|
||||
import { describe, it, expect, jest } from '@jest/globals'
|
||||
import { FetchError, ErrorCode } from "../../src/exceptions"
|
||||
import { robustFetch } from "../../src/fetch"
|
||||
import { describe, it, expect, jest } from '@jest/globals';
|
||||
import { FetchError, ErrorCode } from '../../src/exceptions';
|
||||
import { robustFetch } from '../../src/fetch';
|
||||
import { Response } from 'node-fetch';
|
||||
|
||||
describe("robustFetch", () => {
|
||||
describe('robustFetch', () => {
|
||||
it('should return a response object', async () => {
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Response((status = '200'), 'Hello World')));
|
||||
|
||||
it("should return a response object", async () => {
|
||||
global.fetch = jest.fn(() => (Promise.resolve(new Response(status="200", "Hello World"))));
|
||||
const response = await robustFetch('https://pyscript.net');
|
||||
expect(response).toBeInstanceOf(Response);
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
const response = await robustFetch("https://pyscript.net");
|
||||
expect(response).toBeInstanceOf(Response);
|
||||
expect(response.status).toBe(200);
|
||||
})
|
||||
it('receiving a 404 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Response('Not Found', { status: 404 })));
|
||||
|
||||
it('receiving a 404 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.resolve(new Response("Not Found", {status: 404}))));
|
||||
const url = 'https://pyscript.net/non-existent-page';
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_NOT_FOUND_ERROR,
|
||||
`Fetching from URL ${url} failed with error 404 (Not Found). ` + `Are your filename and path correct?`,
|
||||
);
|
||||
|
||||
const url = "https://pyscript.net/non-existent-page"
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_NOT_FOUND_ERROR,
|
||||
`Fetching from URL ${url} failed with error 404 (Not Found). ` +
|
||||
`Are your filename and path correct?`
|
||||
)
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
it('receiving a 401 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Response('', { status: 401 })));
|
||||
|
||||
it('receiving a 401 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.resolve(new Response("", {status: 401}))));
|
||||
const url = 'https://pyscript.net/protected-page';
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
|
||||
`Fetching from URL ${url} failed with error 401 (Unauthorized). ` + `Are your filename and path correct?`,
|
||||
);
|
||||
|
||||
const url = "https://pyscript.net/protected-page"
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
|
||||
`Fetching from URL ${url} failed with error 401 (Unauthorized). ` +
|
||||
`Are your filename and path correct?`
|
||||
)
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
it('receiving a 403 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Response('', { status: 403 })));
|
||||
|
||||
it('receiving a 403 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.resolve(new Response("", {status: 403}))));
|
||||
const url = 'https://pyscript.net/secret-page';
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_FORBIDDEN_ERROR,
|
||||
`Fetching from URL ${url} failed with error 403 (Forbidden). ` + `Are your filename and path correct?`,
|
||||
);
|
||||
|
||||
const url = "https://pyscript.net/secret-page"
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_FORBIDDEN_ERROR,
|
||||
`Fetching from URL ${url} failed with error 403 (Forbidden). ` +
|
||||
`Are your filename and path correct?`
|
||||
)
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
it('receiving a 500 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Response('Not Found', { status: 500 })));
|
||||
|
||||
it('receiving a 500 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.resolve(new Response("Not Found", {status: 500}))));
|
||||
const url = 'https://pyscript.net/protected-page';
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_SERVER_ERROR,
|
||||
`Fetching from URL ${url} failed with error 500 (Internal Server Error). ` +
|
||||
`Are your filename and path correct?`,
|
||||
);
|
||||
|
||||
const url = "https://pyscript.net/protected-page"
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_SERVER_ERROR,
|
||||
`Fetching from URL ${url} failed with error 500 (Internal Server Error). ` +
|
||||
`Are your filename and path correct?`
|
||||
)
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
it('receiving a 503 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => Promise.resolve(new Response('Not Found', { status: 503 })));
|
||||
|
||||
it('receiving a 503 when fetching should throw FetchError with the right errorCode', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.resolve(new Response("Not Found", {status: 503}))));
|
||||
const url = 'https://pyscript.net/protected-page';
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_UNAVAILABLE_ERROR,
|
||||
`Fetching from URL ${url} failed with error 503 (Service Unavailable). ` +
|
||||
`Are your filename and path correct?`,
|
||||
);
|
||||
|
||||
const url = "https://pyscript.net/protected-page"
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_UNAVAILABLE_ERROR,
|
||||
`Fetching from URL ${url} failed with error 503 (Service Unavailable). ` +
|
||||
`Are your filename and path correct?`
|
||||
)
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
it('handle TypeError when using a bad url', async () => {
|
||||
global.fetch = jest.fn(() => Promise.reject(new TypeError('Failed to fetch')));
|
||||
|
||||
it('handle TypeError when using a bad url', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.reject(new TypeError("Failed to fetch"))));
|
||||
const url = 'https://pyscript.net/protected-page';
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_ERROR,
|
||||
`Fetching from URL ${url} failed with error 'Failed to fetch'. Are your filename and path correct?`,
|
||||
);
|
||||
|
||||
const url = "https://pyscript.net/protected-page"
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_ERROR,
|
||||
`Fetching from URL ${url} failed with error 'Failed to fetch'. Are your filename and path correct?`
|
||||
)
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
it('handle failed to fetch when using local file', async () => {
|
||||
global.fetch = jest.fn(() => Promise.reject(new TypeError('Failed to fetch')));
|
||||
|
||||
it('handle failed to fetch when using local file', async () => {
|
||||
global.fetch = jest.fn(() => (Promise.reject(new TypeError("Failed to fetch"))));
|
||||
const url = './my-awesome-pyscript.py';
|
||||
|
||||
const url = "./my-awesome-pyscript.py"
|
||||
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_ERROR,
|
||||
`PyScript: Access to local files
|
||||
const expectedError = new FetchError(
|
||||
ErrorCode.FETCH_ERROR,
|
||||
`PyScript: Access to local files
|
||||
(using "Paths:" in <py-config>)
|
||||
is not available when directly opening a HTML file;
|
||||
you must use a webserver to serve the additional files.
|
||||
See <a style="text-decoration: underline;" href="https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062">this reference</a>
|
||||
on starting a simple webserver with Python.
|
||||
`
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
})
|
||||
expect(() => robustFetch(url)).rejects.toThrow(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,49 +1,55 @@
|
||||
import { calculatePaths } from "../../src/plugins/fetch";
|
||||
import { FetchConfig } from "../../src/pyconfig";
|
||||
import { calculatePaths } from '../../src/plugins/fetch';
|
||||
import { FetchConfig } from '../../src/pyconfig';
|
||||
|
||||
describe("CalculateFetchPaths", () => {
|
||||
it("should calculate paths when only from is provided", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/data.csv" }];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(["./data.csv"]);
|
||||
expect(fetchPaths).toStrictEqual(["http://a.com/data.csv"]);
|
||||
})
|
||||
describe('CalculateFetchPaths', () => {
|
||||
it('should calculate paths when only from is provided', () => {
|
||||
const fetch_cfg: FetchConfig[] = [{ from: 'http://a.com/data.csv' }];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(['./data.csv']);
|
||||
expect(fetchPaths).toStrictEqual(['http://a.com/data.csv']);
|
||||
});
|
||||
|
||||
it("should calculate paths when only files is provided", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{files: ["foo/__init__.py", "foo/mod.py"] }];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(["./foo/__init__.py", "./foo/mod.py"]);
|
||||
expect(fetchPaths).toStrictEqual(["foo/__init__.py", "foo/mod.py"]);
|
||||
})
|
||||
it('should calculate paths when only files is provided', () => {
|
||||
const fetch_cfg: FetchConfig[] = [{ files: ['foo/__init__.py', 'foo/mod.py'] }];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(['./foo/__init__.py', './foo/mod.py']);
|
||||
expect(fetchPaths).toStrictEqual(['foo/__init__.py', 'foo/mod.py']);
|
||||
});
|
||||
|
||||
it("should calculate paths when files and to_folder is provided", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{files: ["foo/__init__.py", "foo/mod.py"], to_folder: "/my/lib/"}];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(["/my/lib/foo/__init__.py", "/my/lib/foo/mod.py"]);
|
||||
expect(fetchPaths).toStrictEqual(["foo/__init__.py", "foo/mod.py"]);
|
||||
})
|
||||
it('should calculate paths when files and to_folder is provided', () => {
|
||||
const fetch_cfg: FetchConfig[] = [{ files: ['foo/__init__.py', 'foo/mod.py'], to_folder: '/my/lib/' }];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(['/my/lib/foo/__init__.py', '/my/lib/foo/mod.py']);
|
||||
expect(fetchPaths).toStrictEqual(['foo/__init__.py', 'foo/mod.py']);
|
||||
});
|
||||
|
||||
it("should calculate paths when from and files and to_folder is provided", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/download/", files: ["foo/__init__.py", "foo/mod.py"], to_folder: "/my/lib/"}];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(["/my/lib/foo/__init__.py", "/my/lib/foo/mod.py"]);
|
||||
expect(fetchPaths).toStrictEqual(["http://a.com/download/foo/__init__.py", "http://a.com/download/foo/mod.py"]);
|
||||
})
|
||||
it('should calculate paths when from and files and to_folder is provided', () => {
|
||||
const fetch_cfg: FetchConfig[] = [
|
||||
{ from: 'http://a.com/download/', files: ['foo/__init__.py', 'foo/mod.py'], to_folder: '/my/lib/' },
|
||||
];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(['/my/lib/foo/__init__.py', '/my/lib/foo/mod.py']);
|
||||
expect(fetchPaths).toStrictEqual(['http://a.com/download/foo/__init__.py', 'http://a.com/download/foo/mod.py']);
|
||||
});
|
||||
|
||||
it("should error out while calculating paths when filename cannot be determined from 'from'", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{from: "http://google.com/", to_folder: "/tmp"}];
|
||||
expect(()=>calculatePaths(fetch_cfg)).toThrowError("Couldn't determine the filename from the path http://google.com/");
|
||||
})
|
||||
const fetch_cfg: FetchConfig[] = [{ from: 'http://google.com/', to_folder: '/tmp' }];
|
||||
expect(() => calculatePaths(fetch_cfg)).toThrowError(
|
||||
"Couldn't determine the filename from the path http://google.com/",
|
||||
);
|
||||
});
|
||||
|
||||
it("should calculate paths when to_file is explicitly supplied", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/data.csv?version=1", to_file: "pkg/tmp/data.csv"}];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(["./pkg/tmp/data.csv"]);
|
||||
expect(fetchPaths).toStrictEqual(["http://a.com/data.csv?version=1"]);
|
||||
})
|
||||
it('should calculate paths when to_file is explicitly supplied', () => {
|
||||
const fetch_cfg: FetchConfig[] = [{ from: 'http://a.com/data.csv?version=1', to_file: 'pkg/tmp/data.csv' }];
|
||||
const [paths, fetchPaths] = calculatePaths(fetch_cfg);
|
||||
expect(paths).toStrictEqual(['./pkg/tmp/data.csv']);
|
||||
expect(fetchPaths).toStrictEqual(['http://a.com/data.csv?version=1']);
|
||||
});
|
||||
|
||||
it("should error out when both to_file and files parameters are provided", () => {
|
||||
const fetch_cfg: FetchConfig[] = [{from: "http://a.com/data.csv?version=1", to_file: "pkg/tmp/data.csv", files: ["a.py", "b.py"]}];
|
||||
expect(()=>calculatePaths(fetch_cfg)).toThrowError("Cannot use 'to_file' and 'files' parameters together!");
|
||||
})
|
||||
})
|
||||
it('should error out when both to_file and files parameters are provided', () => {
|
||||
const fetch_cfg: FetchConfig[] = [
|
||||
{ from: 'http://a.com/data.csv?version=1', to_file: 'pkg/tmp/data.csv', files: ['a.py', 'b.py'] },
|
||||
];
|
||||
expect(() => calculatePaths(fetch_cfg)).toThrowError("Cannot use 'to_file' and 'files' parameters together!");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,20 +15,16 @@ describe('getLogger', () => {
|
||||
console.info = jest.fn();
|
||||
|
||||
const logger = getLogger('prefix1');
|
||||
logger.info('hello world')
|
||||
expect(console.info).toHaveBeenCalledWith(
|
||||
'[prefix1] hello world'
|
||||
)
|
||||
logger.info('hello world');
|
||||
expect(console.info).toHaveBeenCalledWith('[prefix1] hello world');
|
||||
});
|
||||
|
||||
it('logger.info handles multiple args', () => {
|
||||
console.info = jest.fn();
|
||||
|
||||
const logger = getLogger('prefix2');
|
||||
logger.info('hello', 'world', 1, 2, 3)
|
||||
expect(console.info).toHaveBeenCalledWith(
|
||||
'[prefix2] hello', 'world', 1, 2, 3
|
||||
)
|
||||
logger.info('hello', 'world', 1, 2, 3);
|
||||
expect(console.info).toHaveBeenCalledWith('[prefix2] hello', 'world', 1, 2, 3);
|
||||
});
|
||||
|
||||
it('logger.{debug,warn,error} also works', () => {
|
||||
@@ -43,15 +39,8 @@ describe('getLogger', () => {
|
||||
logger.error('this is an error');
|
||||
|
||||
expect(console.info).not.toHaveBeenCalled();
|
||||
expect(console.debug).toHaveBeenCalledWith(
|
||||
'[prefix3] this is a debug'
|
||||
)
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
'[prefix3] this is a warning'
|
||||
)
|
||||
expect(console.error).toHaveBeenCalledWith(
|
||||
'[prefix3] this is an error'
|
||||
)
|
||||
expect(console.debug).toHaveBeenCalledWith('[prefix3] this is a debug');
|
||||
expect(console.warn).toHaveBeenCalledWith('[prefix3] this is a warning');
|
||||
expect(console.error).toHaveBeenCalledWith('[prefix3] this is an error');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { describe, it, beforeEach, expect } from "@jest/globals"
|
||||
import { UserError, ErrorCode } from "../../src/exceptions"
|
||||
import { PyScriptApp } from "../../src/main"
|
||||
|
||||
describe("Test withUserErrorHandler", () => {
|
||||
import { describe, it, beforeEach, expect } from '@jest/globals';
|
||||
import { UserError, ErrorCode } from '../../src/exceptions';
|
||||
import { PyScriptApp } from '../../src/main';
|
||||
|
||||
describe('Test withUserErrorHandler', () => {
|
||||
class MyApp extends PyScriptApp {
|
||||
myRealMain: any;
|
||||
|
||||
@@ -24,46 +23,46 @@ describe("Test withUserErrorHandler", () => {
|
||||
|
||||
it("userError doesn't stop execution", () => {
|
||||
function myRealMain() {
|
||||
throw new UserError(ErrorCode.GENERIC, "Computer says no");
|
||||
throw new UserError(ErrorCode.GENERIC, 'Computer says no');
|
||||
}
|
||||
|
||||
const app = new MyApp(myRealMain);
|
||||
app.main();
|
||||
const banners = document.getElementsByClassName("alert-banner");
|
||||
const banners = document.getElementsByClassName('alert-banner');
|
||||
expect(banners.length).toBe(1);
|
||||
expect(banners[0].innerHTML).toBe("(PY0000): Computer says no");
|
||||
expect(banners[0].innerHTML).toBe('(PY0000): Computer says no');
|
||||
});
|
||||
|
||||
it("userError escapes by default", () => {
|
||||
it('userError escapes by default', () => {
|
||||
function myRealMain() {
|
||||
throw new UserError(ErrorCode.GENERIC, "hello <br>");
|
||||
throw new UserError(ErrorCode.GENERIC, 'hello <br>');
|
||||
}
|
||||
|
||||
const app = new MyApp(myRealMain);
|
||||
app.main();
|
||||
const banners = document.getElementsByClassName("alert-banner");
|
||||
const banners = document.getElementsByClassName('alert-banner');
|
||||
expect(banners.length).toBe(1);
|
||||
expect(banners[0].innerHTML).toBe("(PY0000): hello <br>");
|
||||
expect(banners[0].innerHTML).toBe('(PY0000): hello <br>');
|
||||
});
|
||||
|
||||
it("userError messageType=html don't escape", () => {
|
||||
function myRealMain() {
|
||||
throw new UserError(ErrorCode.GENERIC, "hello <br>", "html");
|
||||
throw new UserError(ErrorCode.GENERIC, 'hello <br>', 'html');
|
||||
}
|
||||
|
||||
const app = new MyApp(myRealMain);
|
||||
app.main();
|
||||
const banners = document.getElementsByClassName("alert-banner");
|
||||
const banners = document.getElementsByClassName('alert-banner');
|
||||
expect(banners.length).toBe(1);
|
||||
expect(banners[0].innerHTML).toBe("(PY0000): hello <br>");
|
||||
expect(banners[0].innerHTML).toBe('(PY0000): hello <br>');
|
||||
});
|
||||
|
||||
it("any other exception should stop execution and raise", () => {
|
||||
it('any other exception should stop execution and raise', () => {
|
||||
function myRealMain() {
|
||||
throw new Error("Explosions!");
|
||||
throw new Error('Explosions!');
|
||||
}
|
||||
|
||||
const app = new MyApp(myRealMain);
|
||||
expect(() => app.main()).toThrow(new Error("Explosions!"))
|
||||
expect(() => app.main()).toThrow(new Error('Explosions!'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('RemoteInterpreter', () => {
|
||||
let interpreter: InterpreterClient;
|
||||
let stdio: CaptureStdio = new CaptureStdio();
|
||||
beforeAll(async () => {
|
||||
const config: AppConfig = {interpreters: [{src: "../pyscriptjs/node_modules/pyodide/pyodide.js"}]};
|
||||
const config: AppConfig = { interpreters: [{ src: '../pyscriptjs/node_modules/pyodide/pyodide.js' }] };
|
||||
interpreter = new InterpreterClient(config, stdio);
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,31 +4,29 @@ import { type Stdio, CaptureStdio, StdioMultiplexer, TargetedStdio } from '../..
|
||||
describe('CaptureStdio', () => {
|
||||
it('captured streams are initialized to empty string', () => {
|
||||
let stdio = new CaptureStdio();
|
||||
expect(stdio.captured_stdout).toBe("");
|
||||
expect(stdio.captured_stderr).toBe("");
|
||||
expect(stdio.captured_stdout).toBe('');
|
||||
expect(stdio.captured_stderr).toBe('');
|
||||
});
|
||||
|
||||
it('stdout() and stderr() captures', () => {
|
||||
let stdio = new CaptureStdio();
|
||||
stdio.stdout_writeline("hello");
|
||||
stdio.stdout_writeline("world");
|
||||
stdio.stderr_writeline("this is an error");
|
||||
expect(stdio.captured_stdout).toBe("hello\nworld\n");
|
||||
expect(stdio.captured_stderr).toBe("this is an error\n");
|
||||
stdio.stdout_writeline('hello');
|
||||
stdio.stdout_writeline('world');
|
||||
stdio.stderr_writeline('this is an error');
|
||||
expect(stdio.captured_stdout).toBe('hello\nworld\n');
|
||||
expect(stdio.captured_stderr).toBe('this is an error\n');
|
||||
});
|
||||
|
||||
it('reset() works', () => {
|
||||
let stdio = new CaptureStdio();
|
||||
stdio.stdout_writeline("aaa");
|
||||
stdio.stderr_writeline("bbb");
|
||||
stdio.stdout_writeline('aaa');
|
||||
stdio.stderr_writeline('bbb');
|
||||
stdio.reset();
|
||||
expect(stdio.captured_stdout).toBe("");
|
||||
expect(stdio.captured_stderr).toBe("");
|
||||
expect(stdio.captured_stdout).toBe('');
|
||||
expect(stdio.captured_stderr).toBe('');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('StdioMultiplexer', () => {
|
||||
let a: CaptureStdio;
|
||||
let b: CaptureStdio;
|
||||
@@ -44,10 +42,10 @@ describe('StdioMultiplexer', () => {
|
||||
// no listeners, messages are ignored
|
||||
multi.stdout_writeline('out 1');
|
||||
multi.stderr_writeline('err 1');
|
||||
expect(a.captured_stdout).toBe("");
|
||||
expect(a.captured_stderr).toBe("");
|
||||
expect(b.captured_stdout).toBe("");
|
||||
expect(b.captured_stderr).toBe("");
|
||||
expect(a.captured_stdout).toBe('');
|
||||
expect(a.captured_stderr).toBe('');
|
||||
expect(b.captured_stdout).toBe('');
|
||||
expect(b.captured_stderr).toBe('');
|
||||
});
|
||||
|
||||
it('redirects to multiple listeners', () => {
|
||||
@@ -59,11 +57,11 @@ describe('StdioMultiplexer', () => {
|
||||
multi.stdout_writeline('out 2');
|
||||
multi.stderr_writeline('err 2');
|
||||
|
||||
expect(a.captured_stdout).toBe("out 1\nout 2\n");
|
||||
expect(a.captured_stderr).toBe("err 1\nerr 2\n");
|
||||
expect(a.captured_stdout).toBe('out 1\nout 2\n');
|
||||
expect(a.captured_stderr).toBe('err 1\nerr 2\n');
|
||||
|
||||
expect(b.captured_stdout).toBe("out 2\n");
|
||||
expect(b.captured_stderr).toBe("err 2\n");
|
||||
expect(b.captured_stdout).toBe('out 2\n');
|
||||
expect(b.captured_stderr).toBe('err 2\n');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,32 +73,30 @@ describe('TargetedStdio', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
//DOM element to capture stdout and stderr
|
||||
let target_div = document.getElementById("output-id");
|
||||
let target_div = document.getElementById('output-id');
|
||||
|
||||
if (target_div=== null){
|
||||
if (target_div === null) {
|
||||
target_div = document.createElement('div');
|
||||
target_div.id = "output-id";
|
||||
target_div.id = 'output-id';
|
||||
document.body.appendChild(target_div);
|
||||
}
|
||||
else {
|
||||
target_div.innerHTML = "";
|
||||
} else {
|
||||
target_div.innerHTML = '';
|
||||
}
|
||||
|
||||
//DOM element to capture stderr
|
||||
let error_div = document.getElementById("error-id");
|
||||
let error_div = document.getElementById('error-id');
|
||||
|
||||
if (error_div=== null){
|
||||
if (error_div === null) {
|
||||
error_div = document.createElement('div');
|
||||
error_div.id = "error-id";
|
||||
error_div.id = 'error-id';
|
||||
document.body.appendChild(error_div);
|
||||
}
|
||||
else {
|
||||
error_div.innerHTML = "";
|
||||
} else {
|
||||
error_div.innerHTML = '';
|
||||
}
|
||||
|
||||
const tag = document.createElement('div');
|
||||
tag.setAttribute("output", "output-id");
|
||||
tag.setAttribute("stderr", "error-id");
|
||||
tag.setAttribute('output', 'output-id');
|
||||
tag.setAttribute('stderr', 'error-id');
|
||||
|
||||
capture = new CaptureStdio();
|
||||
targeted = new TargetedStdio(tag, 'output', true, true);
|
||||
@@ -112,29 +108,29 @@ describe('TargetedStdio', () => {
|
||||
multi.addListener(error_targeted);
|
||||
});
|
||||
|
||||
it('targeted id is set by constructor', () =>{
|
||||
expect(targeted.source_attribute).toBe("output");
|
||||
it('targeted id is set by constructor', () => {
|
||||
expect(targeted.source_attribute).toBe('output');
|
||||
});
|
||||
|
||||
it('targeted stdio/stderr also goes to multiplexer', () =>{
|
||||
multi.stdout_writeline("out 1");
|
||||
multi.stderr_writeline("out 2");
|
||||
expect(capture.captured_stdout).toBe("out 1\n");
|
||||
expect(capture.captured_stderr).toBe("out 2\n");
|
||||
expect(document.getElementById("output-id")?.innerHTML).toBe("out 1<br>out 2<br>");
|
||||
expect(document.getElementById("error-id")?.innerHTML).toBe("out 2<br>");
|
||||
it('targeted stdio/stderr also goes to multiplexer', () => {
|
||||
multi.stdout_writeline('out 1');
|
||||
multi.stderr_writeline('out 2');
|
||||
expect(capture.captured_stdout).toBe('out 1\n');
|
||||
expect(capture.captured_stderr).toBe('out 2\n');
|
||||
expect(document.getElementById('output-id')?.innerHTML).toBe('out 1<br>out 2<br>');
|
||||
expect(document.getElementById('error-id')?.innerHTML).toBe('out 2<br>');
|
||||
});
|
||||
|
||||
it('Add and remove targeted listener', () => {
|
||||
multi.stdout_writeline("out 1");
|
||||
multi.stdout_writeline('out 1');
|
||||
multi.removeListener(targeted);
|
||||
multi.stdout_writeline("out 2");
|
||||
multi.stdout_writeline('out 2');
|
||||
multi.addListener(targeted);
|
||||
multi.stdout_writeline("out 3");
|
||||
multi.stdout_writeline('out 3');
|
||||
|
||||
//all three should be captured by multiplexer
|
||||
expect(capture.captured_stdout).toBe("out 1\nout 2\nout 3\n");
|
||||
expect(capture.captured_stdout).toBe('out 1\nout 2\nout 3\n');
|
||||
//out 2 should not be present in the DOM element
|
||||
expect(document.getElementById("output-id")?.innerHTML).toBe("out 1<br>out 3<br>");
|
||||
expect(document.getElementById('output-id')?.innerHTML).toBe('out 1<br>out 3<br>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,73 +1,78 @@
|
||||
import { beforeEach, expect, describe, it } from "@jest/globals"
|
||||
import { ensureUniqueId, joinPaths, createSingularWarning} from "../../src/utils"
|
||||
import { beforeEach, expect, describe, it } from '@jest/globals';
|
||||
import { ensureUniqueId, joinPaths, createSingularWarning } from '../../src/utils';
|
||||
|
||||
describe("Utils", () => {
|
||||
describe('Utils', () => {
|
||||
let element: HTMLElement;
|
||||
|
||||
let element: HTMLElement;
|
||||
beforeEach(() => {
|
||||
element = document.createElement('div');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
element = document.createElement("div");
|
||||
})
|
||||
it('ensureUniqueId sets unique id on element', async () => {
|
||||
expect(element.id).toBe('');
|
||||
|
||||
it("ensureUniqueId sets unique id on element", async () => {
|
||||
expect(element.id).toBe("")
|
||||
ensureUniqueId(element);
|
||||
|
||||
ensureUniqueId(element)
|
||||
expect(element.id).toBe('py-internal-0');
|
||||
});
|
||||
|
||||
expect(element.id).toBe("py-internal-0")
|
||||
})
|
||||
it('ensureUniqueId sets unique id with increasing counter', async () => {
|
||||
const secondElement = document.createElement('div');
|
||||
|
||||
it("ensureUniqueId sets unique id with increasing counter", async () => {
|
||||
const secondElement = document.createElement("div")
|
||||
expect(element.id).toBe('');
|
||||
expect(secondElement.id).toBe('');
|
||||
|
||||
expect(element.id).toBe("")
|
||||
expect(secondElement.id).toBe("")
|
||||
ensureUniqueId(element);
|
||||
ensureUniqueId(secondElement);
|
||||
|
||||
ensureUniqueId(element)
|
||||
ensureUniqueId(secondElement)
|
||||
// The counter will have been incremented on
|
||||
// the previous test, make sure it keeps increasing
|
||||
expect(element.id).toBe('py-internal-1');
|
||||
expect(secondElement.id).toBe('py-internal-2');
|
||||
});
|
||||
});
|
||||
|
||||
// The counter will have been incremented on
|
||||
// the previous test, make sure it keeps increasing
|
||||
expect(element.id).toBe("py-internal-1")
|
||||
expect(secondElement.id).toBe("py-internal-2")
|
||||
})
|
||||
})
|
||||
describe('JoinPaths', () => {
|
||||
it('should remove trailing slashes from the beginning and the end', () => {
|
||||
const paths: string[] = ['///abc/d/e///'];
|
||||
const joinedPath = joinPaths(paths);
|
||||
expect(joinedPath).toStrictEqual('/abc/d/e');
|
||||
});
|
||||
|
||||
describe("JoinPaths", () => {
|
||||
it("should remove trailing slashes from the beginning and the end", () => {
|
||||
const paths: string[] = ['///abc/d/e///'];
|
||||
const joinedPath = joinPaths(paths);
|
||||
expect(joinedPath).toStrictEqual('/abc/d/e');
|
||||
})
|
||||
it('should not remove slashes from the middle to preserve protocols such as http', () => {
|
||||
const paths: string[] = ['http://google.com', '///data.txt'];
|
||||
const joinedPath = joinPaths(paths);
|
||||
expect(joinedPath).toStrictEqual('http://google.com/data.txt');
|
||||
});
|
||||
|
||||
it("should not remove slashes from the middle to preserve protocols such as http", () => {
|
||||
const paths: string[] = ['http://google.com', '///data.txt'];
|
||||
const joinedPath = joinPaths(paths);
|
||||
expect(joinedPath).toStrictEqual('http://google.com/data.txt');
|
||||
})
|
||||
it('should not join paths when they are empty strings', () => {
|
||||
const paths: string[] = ['', '///hhh/ll/pp///', '', 'kkk'];
|
||||
const joinedPath = joinPaths(paths);
|
||||
expect(joinedPath).toStrictEqual('hhh/ll/pp/kkk');
|
||||
});
|
||||
|
||||
it("should not join paths when they are empty strings", () => {
|
||||
const paths: string[] = ['', '///hhh/ll/pp///', '', 'kkk'];
|
||||
const joinedPath = joinPaths(paths);
|
||||
expect(joinedPath).toStrictEqual('hhh/ll/pp/kkk');
|
||||
})
|
||||
describe('createSingularBanner', () => {
|
||||
it('should create one and new banner containing the sentinel text, and not duplicate it', () => {
|
||||
//One warning banner with the desired text should be created
|
||||
createSingularWarning('A unique error message', 'unique');
|
||||
expect(document.getElementsByClassName('alert-banner')?.length).toEqual(1);
|
||||
expect(document.getElementsByClassName('alert-banner')[0].textContent).toEqual(
|
||||
expect.stringContaining('A unique error message'),
|
||||
);
|
||||
|
||||
describe("createSingularBanner", () => {
|
||||
it("should create one and new banner containing the sentinel text, and not duplicate it", () => {
|
||||
//One warning banner with the desired text should be created
|
||||
createSingularWarning("A unique error message", "unique")
|
||||
expect(document.getElementsByClassName("alert-banner")?.length).toEqual(1)
|
||||
expect(document.getElementsByClassName("alert-banner")[0].textContent).toEqual(expect.stringContaining("A unique error message"))
|
||||
//Should still only be one banner, since the second uses the existing sentinel value "unique"
|
||||
createSingularWarning('This banner should not appear', 'unique');
|
||||
expect(document.getElementsByClassName('alert-banner')?.length).toEqual(1);
|
||||
expect(document.getElementsByClassName('alert-banner')[0].textContent).toEqual(
|
||||
expect.stringContaining('A unique error message'),
|
||||
);
|
||||
|
||||
//Should still only be one banner, since the second uses the existing sentinel value "unique"
|
||||
createSingularWarning("This banner should not appear", "unique")
|
||||
expect(document.getElementsByClassName("alert-banner")?.length).toEqual(1)
|
||||
expect(document.getElementsByClassName("alert-banner")[0].textContent).toEqual(expect.stringContaining("A unique error message"))
|
||||
|
||||
//If the sentinel value is not provided, the entire msg is used as the sentinel
|
||||
createSingularWarning("A unique error message", null)
|
||||
expect(document.getElementsByClassName("alert-banner")?.length).toEqual(1)
|
||||
expect(document.getElementsByClassName("alert-banner")[0].textContent).toEqual(expect.stringContaining("A unique error message"))
|
||||
})
|
||||
})
|
||||
})
|
||||
//If the sentinel value is not provided, the entire msg is used as the sentinel
|
||||
createSingularWarning('A unique error message', null);
|
||||
expect(document.getElementsByClassName('alert-banner')?.length).toEqual(1);
|
||||
expect(document.getElementsByClassName('alert-banner')[0].textContent).toEqual(
|
||||
expect.stringContaining('A unique error message'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"_version": "3.0.0",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"_version": "3.0.0",
|
||||
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"types": ["jest", "node"],
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom",
|
||||
"DOM.Iterable"
|
||||
]
|
||||
}
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"types": ["jest", "node"],
|
||||
"strict": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": ["es2017", "dom", "DOM.Iterable"]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user