diff --git a/.bowerrc b/.bowerrc index 0ab1766..85967b5 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ -{ - "directory" : "modules" -} \ No newline at end of file +{ + "directory" : "modules" +} diff --git a/.eslintrc.json b/.eslintrc.json index 972b7f2..cf9e113 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,11 +9,8 @@ "key-spacing": "off" }, "extends": [ - "plugin:node/recommended", + "plugin:n/recommended", "plugin:putout/recommended" ], - "plugins": [ - "node", - "putout" - ] + "plugins": ["n", "putout"] } diff --git a/.gitignore b/.gitignore index b9a4e3b..c2a8356 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -package-lock.json -node_modules -npm-debug.log* -.nyc_output *.swp -yarn-error.log -dist +*.log +*.lock +.nyc_output .idea + +package-lock.json +npm-debug.log* + +node_modules +dist coverage diff --git a/.madrun.mjs b/.madrun.js similarity index 74% rename from .madrun.mjs rename to .madrun.js index bf5b3d6..5aae21c 100644 --- a/.madrun.mjs +++ b/.madrun.js @@ -1,4 +1,9 @@ -import {run} from 'madrun'; +import {run, cutEnv} from 'madrun'; +import {defineEnv} from 'supertape/env'; + +const env = defineEnv({ + css: true, +}); export default { 'watch': () => 'nodemon --watch lib --watch test --exec', @@ -6,14 +11,17 @@ export default { 'watch:lint': async () => await run('watch', `'npm run lint'`), 'watch:lint:js': () => run('watch', '"run lint:js"'), 'watch:coverage': () => run('watch', 'redrun coverage'), - 'coverage': () => 'c8 npm test', + 'coverage': async () => [env, `c8 ${await cutEnv('test')}`], 'report': () => 'c8 report --reporter=lcov', 'lint': () => 'putout .', 'fresh:lint': () => run('lint', '--fresh'), 'lint:fresh': () => run('lint', '--fresh'), 'fix:lint': () => run('lint', '--fix'), - 'test': () => `tape --no-check-scopes 'test/**/*.js'`, - 'test:update': () => 'UPDATE_FIXTURE=1 npm test', + 'test': () => [env, `tape --no-check-scopes 'test/**/*.js'`], + 'test:update': async () => [`await cutEnv('test')`, { + UPDATE_FIXTURE: 1, + ...env, + }], 'build': () => 'webpack --progress --mode production', 'wisdom': () => run('build'), 'wisdom:done': () => run('upload:*'), @@ -26,4 +34,3 @@ export default { function upload(name) { return 'putasset -o coderaiser -r smalltalk -t v`version`' + ` -f ${name}`; } - diff --git a/.npmignore b/.npmignore index 29c2db6..909d8f1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,11 @@ -screen +*.config.* +*.log +*.loc + .* -test -yarn-error.log + webpack.config.js +screen +test coverage diff --git a/.putout.json b/.putout.json new file mode 100644 index 0000000..90d6ac7 --- /dev/null +++ b/.putout.json @@ -0,0 +1,10 @@ +{ + "rules": { + "tape/add-t-end": "off" + }, + "match": { + "smalltalk.native.js": { + "arguments/remove-unused": "off" + } + } +} diff --git a/.stylelintrc.yml b/.stylelintrc.yml deleted file mode 100644 index c2ff2e6..0000000 --- a/.stylelintrc.yml +++ /dev/null @@ -1,15 +0,0 @@ -extends: stylelint-config-standard -rules: - indentation: 4 - declaration-block-trailing-semicolon: always - declaration-colon-space-before: null - selector-list-comma-newline-after: null - comment-empty-line-before: null - number-leading-zero: null - number-no-trailing-zeros: null - string-quotes: single - function-url-quotes: never - no-eol-whitespace: null - declaration-empty-line-before: null - max-empty-lines: 2 - diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6b0e076..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: node_js -node_js: - - 15 - - 14 -script: - - npm run lint - - npm run coverage && npm run report -notifications: - email: true -sudo: false -cache: false diff --git a/ChangeLog b/ChangeLog index 5456ffd..dfa9b8a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,50 @@ +2026.03.19, v5.0.1 + +feature: +- 492c630 smalltalk: webpack-cli v7.0.2 +- 2e80306 smalltalk: version-io v6.1.2 +- e41b6da smalltalk: putout v42.2.3 +- fefed49 smalltalk: madrun v13.0.1 +- 0439d8f smalltalk: eslint-plugin-putout v31.1.1 +- 954f062 smalltalk: eslint v10.0.3 +- a28f0fa smalltalk: superc8 v12.3.1 + +2026.02.02, v5.0.0 + +feature: +- db1594d smalltalk: migrate to ESM +- b340642 smalltalk: webpack-cli v6.0.1 +- 003ca38 smalltalk: version-io v5.0.0 +- 59102e9 smalltalk: supertape v12.2.0 +- 3e11e61 smalltalk: style-loader v4.0.0 +- ccbd0ed smalltalk: putout v41.16.0 +- a74682d smalltalk: putasset v7.0.0 +- 7f3e9d1 smalltalk: madrun v12.1.3 +- 3eefd4c smalltalk: fullstore v4.0.0 +- fb12497 smalltalk: eslint-plugin-putout v30.0.2 +- 4db84bc smalltalk: eslint-plugin-n v17.23.2 +- effc8c1 smalltalk: eslint v9.39.2 +- 0424c0d smalltalk: css-loader v7.1.3 +- 78b993b smalltalk: c8 v10.1.3 +- c53349a smalltalk: auto-globals v4.0.1 + +2023.12.03, v4.1.2 + +fix: +- 64f0fc8 export + +feature: +- 13ca47e smalltalk: eslint-plugin-n v16.3.1 +- d4c8994 smalltalk: postcss v8.4.32 +- 6c4b74a drop coveralls +- 0e0345f smalltalk: nodemon v3.0.2 +- cb46477 smalltalk: webpack-cli v5.1.4 +- 924db6f smalltalk: eslint-plugin-putout v21.0.2 +- acc397f smalltalk: putout v33.13.3 +- f7041ab smalltalk: supertape v8.8.0 +- 383d124 smalltalk: c8 v8.0.1 +- 337c31a smalltalk: auto-globals v3.0.0 + 2022.07.13, v4.1.1 fix: diff --git a/README.md b/README.md index 20c2b76..1c1880f 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,17 @@ npm i smalltalk First things first, require `smalltalk` with: ```js -const smalltalk = require('smalltalk'); +import * as smalltalk from 'smalltalk'; ``` You can also use native version with: ```js -const smalltalk = require('smalltalk/native'); +import * as smalltalk from 'smalltalk/native'; ``` When you need a bundled verseion use - ```js import smalltalk from 'smalltalk/bundle'; ``` @@ -102,7 +101,8 @@ smalltalk ```js const progress = smalltalk.progress('Cloud Commander', 'Copy /home/coderaiser -> /home/coderaiser/2'); -progress.setProgress(41) +progress + .setProgress(41) .catch(() => { console.log('abort'); }); @@ -113,8 +113,9 @@ progress.setProgress(41) You can use custom label passing into options param the buttons specification. For example : ```js -const tryToCatch = require('try-to-catch'); +const {tryToCatch} = require('try-to-catch'); const OK = 2; + const result = await tryToCatch(smalltalk.confirm, 'Question', 'Are you sure?', { buttons: { ok: 'Ok Label', diff --git a/css/smalltalk.css b/css/smalltalk.css index a88bd0f..6ec1b14 100644 --- a/css/smalltalk.css +++ b/css/smalltalk.css @@ -1,20 +1,13 @@ .smalltalk { display: flex; - align-items: center; flex-direction: column; justify-content: center; - transition: 200ms opacity; - - bottom: 0; - left: 0; + inset: 0; overflow: auto; padding: 20px; position: fixed; - right: 0; - top: 0; - z-index: 100; } @@ -33,7 +26,7 @@ z-index: 0; } -@media only screen and (max-width: 500px) { +@media only screen and (width <= 500px) { .smalltalk .page { min-width: 0; } @@ -65,7 +58,6 @@ text-overflow: ellipsis; white-space: nowrap; max-width: 500px; - user-select: none; color: #333; font-size: 120%; @@ -78,7 +70,6 @@ .smalltalk .page .content-area { overflow: hidden; text-overflow: ellipsis; - padding: 6px 17px; position: relative; } @@ -100,7 +91,6 @@ button { appearance: none; user-select: none; background-image: linear-gradient(#ededed, #ededed 38%, #dedede); - border: 1px solid rgb(0 0 0 / 25%); border-radius: 2px; box-shadow: 0 1px 0 rgb(0 0 0 / 8%), inset 0 1px 2px rgb(255 255 255 / 75%); @@ -122,7 +112,6 @@ button { .smalltalk .page .button-strip { display: flex; - flex-direction: row; justify-content: flex-end; } diff --git a/lib/smalltalk.js b/lib/smalltalk.js index a655c5e..a45b2cb 100644 --- a/lib/smalltalk.js +++ b/lib/smalltalk.js @@ -1,10 +1,7 @@ -'use strict'; - -require('../css/smalltalk.css'); - -const currify = require('currify'); -const store = require('fullstore'); -const createElement = require('@cloudcmd/create-element'); +import '../css/smalltalk.css'; +import currify from 'currify'; +import createElement from '@cloudcmd/create-element'; +import {fullstore} from 'fullstore'; const isBool = (a) => typeof a === 'boolean'; @@ -19,31 +16,30 @@ const BUTTON_OK_CANCEL = { cancel: 'Cancel', }; -const zIndex = store(100); +const zIndex = fullstore(100); -exports.alert = (title, msg, options) => { +export const alert = (title, msg, options) => { const buttons = getButtons(options) || BUTTON_OK; return showDialog(title, msg, '', buttons, options); }; -exports.prompt = (title, msg, value = '', options) => { +export const prompt = (title, msg, value = '', options) => { const type = getType(options); - const val = String(value) - .replace(/"/g, '"'); + const val = String(value).replace(/"/g, '"'); - const valueStr = ``; + const valueStr = ``; const buttons = getButtons(options) || BUTTON_OK_CANCEL; return showDialog(title, msg, valueStr, buttons, options); }; -exports.confirm = (title, msg, options) => { +export const confirm = (title, msg, options) => { const buttons = getButtons(options) || BUTTON_OK_CANCEL; return showDialog(title, msg, '', buttons, options); }; -exports.progress = (title, message, options) => { +export const progress = (title, message, options) => { const valueStr = ` 0% @@ -106,8 +102,8 @@ function getTemplate(title, msg, value, buttons) { return `
-
${ title }
-
${ encodedMsg }${ value }
+
${title}
+
${encodedMsg}${value}
${parseButtons(buttons)} @@ -130,8 +126,8 @@ function parseButtons(buttons) { } function showDialog(title, msg, value, buttons, options) { - const ok = store(); - const cancel = store(); + const ok = fullstore(); + const cancel = fullstore(); const closeButtons = [ 'cancel', @@ -170,6 +166,7 @@ function showDialog(title, msg, value, buttons, options) { for (const event of ['click', 'contextmenu']) dialog.addEventListener(event, (e) => { e.stopPropagation(); + for (const el of find(dialog, ['ok', 'input'])) el.focus(); }); @@ -184,21 +181,25 @@ function showDialog(title, msg, value, buttons, options) { function keyDown_(dialog, ok, cancel, event) { const KEY = { - ENTER : 13, - ESC : 27, - TAB : 9, - LEFT : 37, - UP : 38, - RIGHT : 39, - DOWN : 40, + ENTER: 13, + ESC: 27, + TAB: 9, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }; const {keyCode} = event; const el = event.target; - const namesAll = ['ok', 'cancel', 'input']; - const names = find(dialog, namesAll) - .map(getDataName); + const namesAll = [ + 'ok', + 'cancel', + 'input', + ]; + + const names = find(dialog, namesAll).map(getDataName); switch(keyCode) { case KEY.ENTER: @@ -220,9 +221,16 @@ function keyDown_(dialog, ok, cancel, event) { break; default: - ['left', 'right', 'up', 'down'].filter((name) => keyCode === KEY[name.toUpperCase()]).forEach(() => { - changeButtonFocus(dialog, names); - }); + [ + 'left', + 'right', + 'up', + 'down', + ] + .filter((name) => keyCode === KEY[name.toUpperCase()]) + .forEach(() => { + changeButtonFocus(dialog, names); + }); break; } @@ -295,14 +303,14 @@ function closeDialog(el, dialog, ok, cancel) { let value = null; for (const el of find(dialog, ['input'])) { - value = el.value; + ({value} = el); } ok(value); remove(dialog); } -const query = currify((element, name) => element.querySelector(`[data-name="js-${ name }"]`)); +const query = currify((element, name) => element.querySelector(`[data-name="js-${name}"]`)); function find(element, names) { const elements = names @@ -324,4 +332,3 @@ function remove(dialog) { if (parentElement) parentElement.removeChild(dialog); } - diff --git a/lib/smalltalk.native.js b/lib/smalltalk.native.js index a5bbeb9..d6acf8a 100644 --- a/lib/smalltalk.native.js +++ b/lib/smalltalk.native.js @@ -1,8 +1,8 @@ -'use strict'; +import '../css/smalltalk.css'; -require('../css/smalltalk.css'); - -exports.alert = (title, message) => { +export const alert = (title, message, overrides = {}) => { + const {alert = globalThis.alert} = overrides; + const promise = new Promise((resolve) => { alert(message); resolve(); @@ -11,8 +11,10 @@ exports.alert = (title, message) => { return promise; }; -exports.prompt = (title, message, value, options) => { +export const prompt = (title, message, value, options, overrides = {}) => { + const {prompt = globalThis.prompt} = overrides; const o = options; + const promise = new Promise((resolve, reject) => { const noCancel = o && !o.cancel; const result = prompt(message, value); @@ -29,9 +31,12 @@ exports.prompt = (title, message, value, options) => { return promise; }; -exports.confirm = (title, message, options) => { +export const confirm = (title, message, options, overrides = {}) => { + const {confirm = globalThis.confirm} = overrides; + const o = options; const noCancel = o && !o.cancel; + const promise = new Promise((resolve, reject) => { const is = confirm(message); @@ -46,4 +51,3 @@ exports.confirm = (title, message, options) => { return promise; }; - diff --git a/package.json b/package.json index 5501812..4e51e4d 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,16 @@ { "name": "smalltalk", - "version": "4.1.1", - "type": "commonjs", - "commitType": "colon", + "version": "5.0.1", + "type": "module", "description": "Promise-based Alert, Confirm and Prompt replacement", "homepage": "http://github.com/coderaiser/smalltalk", "repository": { "type": "git", - "url": "git://github.com/coderaiser/smalltalk.git" + "url": "git+https://github.com/coderaiser/smalltalk.git" }, "main": "lib/smalltalk.js", "exports": { - ".": "./lib/smalltalk", - "./bundle": "./dist/smalltalk.min.js" + ".": "./lib/smalltalk.js" }, "scripts": { "watch": "madrun watch", @@ -48,34 +46,34 @@ "url": "https://github.com/coderaiser/smalltalk/issues" }, "devDependencies": { - "auto-globals": "^2.0.0", + "auto-globals": "^4.0.1", "autoprefixer": "^10.0.1", - "c8": "^7.11.3", "clean-css-loader": "^4.1.1", - "coveralls": "^3.0.0", - "css-loader": "^6.7.1", + "css-loader": "^7.1.3", "css-modules-require-hook": "^4.2.3", - "eslint": "^8.19.0", - "eslint-plugin-node": "^11.0.0", - "eslint-plugin-putout": "^15.8.0", - "madrun": "^9.0.5", - "nodemon": "^2.0.1", - "putasset": "^6.0.0", - "putout": "^26.24.0", - "style-loader": "^3.3.1", - "supertape": "^7.6.0", + "eslint": "^10.0.3", + "eslint-plugin-n": "^17.23.2", + "eslint-plugin-putout": "^31.1.1", + "madrun": "^13.0.1", + "nodemon": "^3.0.2", + "postcss": "^8.4.32", + "putasset": "^7.0.0", + "putout": "^42.2.3", + "style-loader": "^4.0.0", + "superc8": "^12.3.1", + "supertape": "^12.2.0", "terser-webpack-plugin": "^5.0.0", "try-to-tape": "^1.1.0", "url-loader": "^4.0.0", - "version-io": "^4.0.1", + "version-io": "^6.1.2", "webpack": "^5.1.3", - "webpack-cli": "^4.0.0", + "webpack-cli": "^7.0.2", "wraptile": "^3.0.0" }, "dependencies": { "@cloudcmd/create-element": "^2.0.0", "currify": "^4.0.0", - "fullstore": "^3.0.0" + "fullstore": "^4.0.0" }, "publishConfig": { "access": "public" diff --git a/test/smalltalk.js b/test/smalltalk.js index c5b64a2..5daa769 100644 --- a/test/smalltalk.js +++ b/test/smalltalk.js @@ -1,26 +1,19 @@ -'use strict'; - -const path = require('path'); -const fs = require('fs'); - -require('css-modules-require-hook/preset'); - -const autoGlobals = require('auto-globals'); -const tape = require('supertape'); -const stub = require('@cloudcmd/stub'); -const currify = require('currify'); -const wraptile = require('wraptile'); - -global.window = {}; +import process from 'node:process'; +import fs from 'node:fs'; +import autoGlobals from 'auto-globals'; +import {test as tape} from 'supertape'; +import currify from 'currify'; +import wraptile from 'wraptile'; +import * as smalltalk from '../lib/smalltalk.js'; +globalThis.window = {}; const {UPDATE_FIXTURE} = process.env; - -const smalltalk = require('../lib/smalltalk'); const noop = () => {}; const isUpdateFixtures = UPDATE_FIXTURE === 'true' || UPDATE_FIXTURE === '1'; const {create} = autoGlobals; const test = autoGlobals(tape); -const fixtureDir = path.join(__dirname, 'fixture'); +const fixtureDir = new URL('fixture', import.meta.url).pathname; +const {stub} = tape; const writeFixture = (name, data) => { return fs.writeFileSync(`${fixtureDir}/${name}.html`, data); @@ -28,6 +21,7 @@ const writeFixture = (name, data) => { const readFixture = (name) => { const fn = () => fs.readFileSync(`${fixtureDir}/${name}.html`, 'utf8'); + fn.update = !isUpdateFixtures ? noop : currify(writeFixture, name); return fn; @@ -88,16 +82,15 @@ test('smalltalk: alert: click', (t, {document}) => { createElement.returns(el); smalltalk.alert('title', 'message'); - t.equal(ok.addEventListener.args.pop()[0], 'click', 'should set click listener'); + const [result] = ok.addEventListener.args.pop(); + + t.equal(result, 'click', 'should set click listener'); t.end(); }); test('smalltalk: alert: close: remove', (t, {document}) => { const parentElement = create(); - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const el = { ...create(), @@ -125,15 +118,16 @@ test('smalltalk: alert: close: remove', (t, {document}) => { target: ok, }); - t.equal(parentElement.removeChild.args.pop().pop(), el, 'should find smalltalk'); + t.equal(parentElement + .removeChild + .args + .pop() + .pop(), el, 'should find smalltalk'); t.end(); }); test('smalltalk: alert: keydown: stopPropagation', (t, {document}) => { - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const parentElement = create(); @@ -157,7 +151,11 @@ test('smalltalk: alert: keydown: stopPropagation', (t, {document}) => { smalltalk.alert('title', 'message'); - const [, keydown] = el.addEventListener.args.filter(([event]) => event === 'keydown').pop(); + const [, keydown] = el + .addEventListener + .args + .filter(([event]) => event === 'keydown') + .pop(); const event = { stopPropagation: stub(), @@ -170,10 +168,7 @@ test('smalltalk: alert: keydown: stopPropagation', (t, {document}) => { }); test('smalltalk: alert: click: stopPropagation: called', (t, {document}) => { - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const parentElement = create(); const el = { @@ -196,10 +191,14 @@ test('smalltalk: alert: click: stopPropagation: called', (t, {document}) => { smalltalk.alert('title', 'message'); - const [, click] = el.addEventListener.args.filter((a) => { + const [, click] = el + .addEventListener + .args + .filter((a) => { const [event] = a; return event === 'click'; - }).pop(); + }) + .pop(); const event = { stopPropagation: stub(), @@ -243,7 +242,9 @@ test('smalltalk: alert: keydown: tab: preventDefault', (t, {document}) => { smalltalk.alert('title', 'message'); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -270,6 +271,7 @@ test('smalltalk: alert: keydown: tab: active name', (t, {document}) => { } = document; const parentElement = create(); + const el = { ...create(), parentElement, @@ -286,7 +288,9 @@ test('smalltalk: alert: keydown: tab: active name', (t, {document}) => { smalltalk.alert('title', 'message'); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -314,6 +318,7 @@ test('smalltalk: alert: keydown: left: focus', (t) => { } = document; const parentElement = create(); + const el = { ...create(), parentElement, @@ -335,7 +340,9 @@ test('smalltalk: alert: keydown: left: focus', (t) => { smalltalk.alert('title', 'message'); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -355,10 +362,7 @@ test('smalltalk: alert: keydown: left: focus', (t) => { }); test('smalltalk: alert: click: focus', (t, {document}) => { - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const parentElement = create(); const el = { @@ -381,7 +385,9 @@ test('smalltalk: alert: click: focus', (t, {document}) => { smalltalk.alert('title', 'message'); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'click') .pop(); @@ -454,7 +460,8 @@ test('smalltalk: confirm: click on close', (t) => { document.createElement.returns(el); document.querySelector.returns(el); - smalltalk.confirm('title', 'message') + smalltalk + .confirm('title', 'message') .catch((e) => { t.ok(e, 'should reject'); t.end(); @@ -500,7 +507,9 @@ test('smalltalk: confirm: keydown: left: active name', (t, {document}) => { smalltalk.confirm('title', 'message'); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -559,7 +568,9 @@ test('smalltalk: confirm: keydown: left: active name: cancel', (t, {document}) = smalltalk.confirm('title', 'message'); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -580,10 +591,7 @@ test('smalltalk: confirm: keydown: left: active name: cancel', (t, {document}) = test('smalltalk: confirm: keydown: esc: reject', (t, {document}) => { const parentElement = create(); - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const el = { ...create(), @@ -603,13 +611,16 @@ test('smalltalk: confirm: keydown: esc: reject', (t, {document}) => { createElement.returns(el); querySelector.returns(el); - smalltalk.confirm('title', 'message') + smalltalk + .confirm('title', 'message') .catch((e) => { t.ok(e, 'should reject'); t.end(); }); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -627,10 +638,7 @@ test('smalltalk: confirm: keydown: esc: reject', (t, {document}) => { test('smalltalk: confirm: keydown: enter', (t, {document}) => { const parentElement = create(); - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const el = { ...create(), @@ -650,13 +658,16 @@ test('smalltalk: confirm: keydown: enter', (t, {document}) => { createElement.returns(el); querySelector.returns(el); - smalltalk.confirm('title', 'message') + smalltalk + .confirm('title', 'message') .then(() => { t.pass('should resolve'); t.end(); }); - const [, keydown] = el.addEventListener.args + const [, keydown] = el + .addEventListener + .args .filter(([event]) => event === 'keydown') .pop(); @@ -736,10 +747,7 @@ test('smalltalk: prompt: no value', (t, {document}) => { }); test('smalltalk: prompt: click on ok', (t, {document}) => { - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const dataName = (a) => `[data-name="js-${a}"]`; @@ -769,7 +777,8 @@ test('smalltalk: prompt: click on ok', (t, {document}) => { createElement.returns(el); querySelector.returns(el); - smalltalk.prompt('title', 'message', value) + smalltalk + .prompt('title', 'message', value) .then((result) => { t.equal(result, value, 'should return value'); t.end(); @@ -784,12 +793,10 @@ test('smalltalk: prompt: click on ok', (t, {document}) => { test('smalltalk: prompt: click on cancel', (t, {document}) => { const dataName = (a) => `[data-name="js-${a}"]`; - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const value = 'hello'; + const input = { ...create(), value, @@ -815,7 +822,8 @@ test('smalltalk: prompt: click on cancel', (t, {document}) => { createElement.returns(el); querySelector.returns(el); - smalltalk.prompt('title', 'message', value) + smalltalk + .prompt('title', 'message', value) .catch((e) => { t.ok(e, 'should reject'); t.end(); @@ -830,12 +838,10 @@ test('smalltalk: prompt: click on cancel', (t, {document}) => { test('smalltalk: prompt: click on cancel: cancel false', (t, {document}) => { const dataName = (a) => `[data-name="js-${a}"]`; - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const value = 'hello'; + const input = { ...create(), value, @@ -864,7 +870,10 @@ test('smalltalk: prompt: click on cancel: cancel false', (t, {document}) => { const fail = t.fail.bind(t); const end = t.end.bind(t); - smalltalk.prompt('title', 'message', value, {cancel: false}) + smalltalk + .prompt('title', 'message', value, { + cancel: false, + }) .then(wraptile(fail, 'should not pass')) .catch(wraptile(fail, 'should not reject')) .then(end); @@ -881,12 +890,10 @@ test('smalltalk: prompt: click on cancel: cancel false', (t, {document}) => { test('smalltalk: prompt: click on cancel: options: no cancel', (t, {document}) => { const dataName = (a) => `[data-name="js-${a}"]`; - const { - createElement, - querySelector, - } = document; + const {createElement, querySelector} = document; const value = 'hello'; + const input = { ...create(), value, @@ -912,7 +919,8 @@ test('smalltalk: prompt: click on cancel: options: no cancel', (t, {document}) = createElement.returns(el); querySelector.returns(el); - smalltalk.prompt('title', 'message', value, {}) + smalltalk + .prompt('title', 'message', value, {}) .catch((e) => { t.ok(e, 'should reject'); t.end(); @@ -976,6 +984,7 @@ test('smalltalk: progress: setProgress', (t, {document}) => { test('smalltalk: progress: setProgress: 100', (t, {document}) => { const valueEl = create(); + valueEl.parentElement = create(); document.querySelector.returns(valueEl); @@ -996,6 +1005,7 @@ test('smalltalk: progress: setProgress: 100', (t, {document}) => { test('smalltalk: progress: remove', (t, {document}) => { const valueEl = create(); + valueEl.parentElement = create(); document.querySelector.returns(valueEl); @@ -1016,4 +1026,3 @@ test('smalltalk: progress: remove', (t, {document}) => { t.calledWith(removeChild, [el], 'should call removeChild'); t.end(); }); - diff --git a/test/smalltalk.native.js b/test/smalltalk.native.js index 8acaba3..4e104c0 100644 --- a/test/smalltalk.native.js +++ b/test/smalltalk.native.js @@ -1,88 +1,73 @@ -'use strict'; +import {test, stub} from 'supertape'; +import {tryToCatch} from 'try-to-catch'; +import * as smalltalk from '../lib/smalltalk.native.js'; -const { - test, - stub, -} = require('supertape'); +globalThis.window = {}; -const smalltalk = require('../lib/smalltalk.native'); - -global.window = {}; - -test('smalltalk.native: Promise', (t) => { - global.window.Promise = null; - reload(); - - t.pass('load with no Promise support'); - t.end(); -}); - -test('smalltalk.native: alert', (t) => { +test('smalltalk.native: alert', async (t) => { const alert = stub(); - global.alert = alert; - smalltalk.alert('title', 'message'); + await smalltalk.alert('title', 'message', { + alert, + }); t.calledWith(alert, ['message'], 'alert should have been called with message'); t.end(); }); -test('smalltalk.native: alert: result', (t) => { - global.alert = stub(); +test('smalltalk.native: alert: result', async (t) => { + const alert = stub(); - smalltalk.alert('title', 'message').then(() => { - t.pass('promise should have been resolved'); - t.end(); - }) - .catch((e) => { - t.fail(`should not reject ${e.message}`); - }); + await smalltalk.alert('title', 'message', { + alert, + }); + + t.pass('promise should have been resolved'); + t.end(); }); -test('smalltalk.native: confirm', (t) => { +test('smalltalk.native: confirm', async (t) => { const confirm = stub().returns(false); - global.confirm = confirm; - smalltalk.confirm('title', 'message') - .catch(() => { - t.calledWith(confirm, ['message'], 'confirm should have been called with message'); - t.end(); - }); + await tryToCatch(smalltalk.confirm, 'title', 'message', null, { + confirm, + }); + + t.calledWith(confirm, ['message'], 'confirm should have been called with message'); + t.end(); }); test('smalltalk.native: confirm: result: ok', (t) => { - global.confirm = stub().returns(true); + globalThis.confirm = stub().returns(true); - smalltalk.confirm('title', 'message').then(() => { - t.pass('should resolve'); - t.end(); - }) - .catch((e) => { - t.notOk(e, 'should not reject'); - }); + smalltalk.confirm('title', 'message'); + + t.pass('should resolve'); + t.end(); }); -test('smalltalk.native: confirm: result: cancel', (t) => { - global.confirm = stub().returns(false); +test('smalltalk.native: confirm: result: cancel', async (t) => { + globalThis.confirm = stub().returns(false); - smalltalk.confirm('title', 'message').then(() => { - t.fail('should not resolve'); - t.end(); - }) - .catch((e) => { - t.ok(e, 'should reject'); - }); + const [e] = await tryToCatch(smalltalk.confirm, 'title', 'message'); + + t.ok(e, 'should reject'); + t.end(); }); test('smalltalk.native: confirm: options: cancel', (t) => { - global.confirm = stub().returns(false); + globalThis.confirm = stub().returns(false); const cancel = false; - smalltalk.confirm('title', 'message', {cancel}).then(() => { - t.fail('should not resolve'); - t.end(); - }) + smalltalk + .confirm('title', 'message', { + cancel, + }) + .then(() => { + t.fail('should not resolve'); + t.end(); + }) .catch(() => { t.fail('should not reject'); }); @@ -92,59 +77,47 @@ test('smalltalk.native: confirm: options: cancel', (t) => { test('smalltalk.native: prompt', (t) => { const prompt = stub(); - global.prompt = prompt; - smalltalk.prompt('title', 'message', 'value'); + smalltalk.prompt('title', 'message', 'value', null, { + prompt, + }); t.calledWith(prompt, ['message', 'value'], 'prompt should have been called with message'); t.end(); }); test('smalltalk.native: prompt: result: ok', (t) => { - global.prompt = stub().returns('hello'); + globalThis.prompt = stub().returns('hello'); - smalltalk.prompt('title', 'message', 'value').then((value) => { - t.equal(value, 'hello', 'should resolve value'); - t.end(); - }) + smalltalk + .prompt('title', 'message', 'value') + .then((value) => { + t.equal(value, 'hello', 'should resolve value'); + t.end(); + }) .catch((e) => { t.fail(`should not reject ${e.message}`); }); }); -test('smalltalk.native: prompt: result: cancel', (t) => { - global.prompt = stub().returns(null); +test('smalltalk.native: prompt: result: cancel', async (t) => { + const prompt = stub().returns(null); - smalltalk.prompt('title', 'message', 'value').then(() => { - t.fail('should not resolve'); - t.end(); - }) - .catch((e) => { - t.ok(e, 'should reject'); - }); + await tryToCatch(smalltalk.prompt, 'title', 'message', 'value', null, { + prompt, + }); + + t.pass('should reject'); + t.end(); }); test('smalltalk.native: prompt: options: cancel', (t) => { - global.prompt = stub().returns(null); - - smalltalk.prompt('title', 'message', 'value', { - cancel: false, - }).then(() => { - t.fail('should not resolve'); - t.end(); - }) - .catch((e) => { - t.fail(`should not reject ${e.message}`); - }); + const prompt = stub().returns(null); + + smalltalk.prompt('title', 'message', 'value', {cancel: false}, { + prompt, + }); t.pass('should do nothing'); + t.end(); }); - -function reload() { - clean(); - return require('../lib/smalltalk.native'); -} - -function clean() { - delete require.cache[require.resolve('../lib/smalltalk.native')]; -} diff --git a/webpack.config.js b/webpack.config.js index ebf11a7..f12ab59 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,9 @@ -'use strict'; - -const path = require('path'); -const TerserPlugin = require('terser-webpack-plugin'); +import path, {dirname} from 'node:path'; +import {fileURLToPath} from 'node:url'; +import TerserPlugin from 'terser-webpack-plugin'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); const dir = './lib'; const dist = path.resolve(__dirname, 'dist'); @@ -10,7 +11,11 @@ const devtool = 'source-map'; const rules = [{ test: /\.css$/, - use: ['style-loader', 'css-loader', 'clean-css-loader'], + use: [ + 'style-loader', + 'css-loader', + 'clean-css-loader', + ], }, { test: /\.(png|gif|svg|woff|woff2|eot|ttf)$/, use: [{ @@ -32,7 +37,7 @@ const optimization = { const filename = `[name].min.js`; -module.exports = { +export default { devtool, entry: { 'smalltalk': `${dir}/smalltalk.js`,