From 51025a3fe01705537ee87d855e59b078b44c5014 Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 10:38:06 +0200 Subject: [PATCH 1/7] test: add indevidual test controlls --- build.js | 3 +- dev/app.tsx | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++-- dev/sw.js | 1 - 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/build.js b/build.js index 2ba8c292..66ba0927 100644 --- a/build.js +++ b/build.js @@ -15,7 +15,7 @@ const buildOpts = { format: 'esm', sourcemap: false, minify: true, - jsxDev: false, // MODE === 'dev', + jsxDev: process.env.NODE_ENV === 'development', jsx: 'automatic', } @@ -55,7 +55,6 @@ if (process.env.NODE_ENV !== 'development') { await ctx.watch() let {port} = await ctx.serve({servedir: distOutdir}) open(`http://localhost:${port}`) - await ctx.dispose() } process.on('unhandledRejection', console.error) diff --git a/dev/app.tsx b/dev/app.tsx index bddb5b9d..a25e313d 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -232,11 +232,55 @@ function ChangeSrc({renderId}) { ) } +function TestCheckBox({name, id, checked, onChange}) { + return ( +
  • + +
  • + ) +} + +const getUrlTestCases = () => { + const urlParams = new URLSearchParams(window.location.search) + if (!urlParams.has('test') || !urlParams.get('test')) return [] + const uniqueTests = [...new Set(urlParams.get('test')?.split('-'))] + return uniqueTests + .map((test) => parseInt(test, 10)) + .filter((test) => !isNaN(test)) +} + +const updateUrlState = (activeTests) => { + const url = new URL(window.location.href) + const urlParams = new URLSearchParams(url.searchParams) + const nextState = activeTests.join('-') + if (urlParams.get('test') === nextState) return + urlParams.set('test', nextState) + url.search = urlParams.toString() + window.history.pushState({}, '', url.toString()) +} + +const pushToUrlState = (id, include) => { + const testCases = getUrlTestCases() + + let nextTestCases + // console.log('pushToUrlState', id, include, testCases) + if (!include) { + nextTestCases = testCases.filter((testCase) => testCase !== id) + } else { + nextTestCases = [...testCases, id].sort() + } + // console.log('setting test cases', nextTestCases, nextTestCases.join('-')) + updateUrlState(nextTestCases) +} + function App() { const imageOn404 = 'https://i9.ytimg.com/s_p/OLAK5uy_mwasty2cJpgWIpr61CqWRkHIT7LC62u7s/sddefault.jpg?sqp=CJz5ye8Fir7X7AMGCNKz4dEF&rs=AOn4CLC-JNn9jj-oFw94oM574w36xUL1iQ&v=5a3859d2' const tmdbImg = 'https://image.tmdb.org/t/p/w500/kqjL17yufvn9OVLyXYpvtyrFfask.jpg' + const locationRef = useRef(window.location.href) // http://i.imgur.com/ozEaj1Z.jpg const rand1 = randSeconds(1, 8) @@ -246,13 +290,32 @@ function App() { const rand5 = randSeconds(2, 10) const [renderId, setRenderId] = useState(Math.random()) const [swRegistered, setSwRegistered] = useState(false) + const [testCases, setTestCases] = useState(getUrlTestCases()) + console.log('testCases', testCases) useLayoutEffect(() => { - navigator.serviceWorker.ready.then(() => { - setSwRegistered(true) - }) + navigator.serviceWorker.ready.then(() => setSwRegistered(true)) }, []) + useEffect(() => { + // monkey patch pushState to catch url changes + var pushState = history.pushState + history.pushState = function () { + pushState.apply(history, arguments) + setTestCases(getUrlTestCases()) + } + + // run once on mount + console.log('onMount', getUrlTestCases()) + updateUrlState(getUrlTestCases()) + }, []) + + const testIsActive = (id) => testCases.includes(id) + const testOnClick = (id) => (e) => { + e.stopPropagation() + pushToUrlState(id, e.target.checked) + } + if (!swRegistered) return
    Waiting for server...
    return ( @@ -288,6 +351,38 @@ function App() {
    +

    +

    +
    +
    +

    Tests to run:

    +
      + + + + +
    +
    @@ -306,7 +401,7 @@ function App() {

    Should not show anything

    ✅ test passed} /> diff --git a/dev/sw.js b/dev/sw.js index 7241d969..3e123171 100644 --- a/dev/sw.js +++ b/dev/sw.js @@ -16,7 +16,6 @@ self.addEventListener('fetch', async (event) => { const url = new URL(event.request.url) if (!event.request.url.startsWith(url.origin + '/delay/')) { - console.log('not delaying', event.request.url) return fetch(event.request) } From d163ad373704ae19bde7787bcb2dc04b00fdf912 Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 11:59:26 +0200 Subject: [PATCH 2/7] list all tests --- dev/app.tsx | 554 +++++++++++++++++++++++++++++----------------------- 1 file changed, 308 insertions(+), 246 deletions(-) diff --git a/dev/app.tsx b/dev/app.tsx index a25e313d..ac6ce3b4 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -9,6 +9,30 @@ import {createRoot} from 'react-dom/client' import {Img, useImage} from '../src/index' import {ErrorBoundary} from './ErrorBoundry' +const pageStyles = { + __html: `img { border: 5px solid green;} + h3 {padding-top: 20px;} + body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + } + .pageContainer { + display: flex; + flex-direction: row-reverse; + } + .testCases { + margin: 10px; + flex: 0 0 80%; + display: grid; + grid-template-columns: auto auto auto auto; + grid-template-rows: auto auto auto auto; + } + .testCase { + padding: 10px; + border: lightgrey solid 1px; + } + `, +} + navigator.serviceWorker.register('/sw.js', {scope: './'}) new EventSource('/esbuild').addEventListener('change', () => location.reload()) @@ -75,96 +99,101 @@ function GlobalTimer({until}) { ) } -const HooksLegacyExample = ({rand}) => { - const {src, isLoading, error} = useImage({ - srcList: [ - 'https://www.example.com/non-existant-image.jpg', - `/delay/${rand * 1000}/https://picsum.photos/200`, // will be loaded - ], - useSuspense: false, - }) +function SelectorCheckBox({name, id, checked, onChange}) { + return ( +
  • + +
  • + ) +} +function TestContainer({name, isActive, id, Test, props}) { + if (!isActive) return null return ( -
    -

    Using hooks Legacy

    - - {isLoading &&
    Loading...
    } - {error &&
    Error! {error.msg}
    } - {src && } - {!isLoading && !error && !src && ( -
    Nothing to show - thats not good!
    - )} +
    +

    {name}

    +
    ) } -const HooksSuspenseExample = ({rand}) => { - const {src} = useImage({ - srcList: [ - 'https://www.example.com/foo.png', - `/delay/${rand * 1000}/https://picsum.photos/200`, // will be loaded - ], - }) +const getUrlTestCases = () => { + const urlParams = new URLSearchParams(window.location.search) + if (!urlParams.has('test') || !urlParams.get('test')) return [] + const uniqueTests = [...new Set(urlParams.get('test')?.split('-'))] + return uniqueTests + .map((test) => parseInt(test, 10)) + .filter((test) => !isNaN(test)) +} + +const updateUrlState = (activeTests) => { + const url = new URL(window.location.href) + const urlParams = new URLSearchParams(url.searchParams) + const nextState = activeTests.join('-') + if (activeTests.length === 0) { + urlParams.delete('test') + } else { + urlParams.set('test', nextState) + } + // if (urlParams.get('test') === nextState) return + url.search = urlParams.toString() + window.history.pushState({}, '', url.toString()) +} + +const updateTestCases = (id, include, testList) => { + let activeCases = getUrlTestCases() + + // tests aren't run if they arent in the list. However we need to have a list + // otherwise we'll run _all_ tests. Here, we set the list to all tests if + // there are no active tests + if (!activeCases.length) activeCases = testList.map((test) => test.id) + + let nextTestCases + // console.log('pushToUrlState', id, include, testCases) + if (!include) { + nextTestCases = activeCases.filter((testCase) => testCase !== id) + } else { + nextTestCases = [...activeCases, id].sort() + } + // console.log('setting test cases', nextTestCases, nextTestCases.join('-')) + updateUrlState(nextTestCases) +} +// begin test cases +function TestShouldShow({rand1}) { return ( -
    - -
    + <> + + Loading...
    } + unloader={
    ❎ test failed
    } + /> + ) } -const ReuseCache = ({renderId}) => { - const src = `https://picsum.photos/200?rand=${renderId}` - const [networkCalls, setNetworkCalls] = useState(0) - - useEffect(() => { - setTimeout(() => { - const entires = performance.getEntriesByName(src) - setNetworkCalls(entires.length) - }, 1000) - }) +function TestShouldNotShowAnything({}) { + return ( + ✅ test passed} /> + ) +} +function ShouldShowUnloader({}) { return ( -
    -

    Suspense should reuse cache and only make one network call

    -
    - {networkCalls < 1 && ❓ test pending} - {networkCalls === 1 && ✅ test passed} - {networkCalls > 1 && ( - - ❌ test failed. If DevTools is open, ensure "Disable Cache" in the - network tab is disabled - - )} -
    -
    Network Calls detected: {networkCalls} (expecting just 1)
    -
    -
    - To test this manually, check the Network Tab in DevTools to ensure the - url - {src} was only called once -
    -
    -
    - - Loading... (Suspense fallback)
    }> - - - - - + Loading...} + unloader={
    ✅ test passed
    } + /> ) } -function ChangeSrc({renderId}) { +function TestChangeSrc({renderId}) { const getSrc = () => { const rand = randSeconds(500, 900) return `https://picsum.photos/200?rand=${rand}` @@ -198,9 +227,6 @@ function ChangeSrc({renderId}) { return ( <> -

    - Change src -

    {loadedSecondSource === null && ❓ test pending} {loadedSecondSource === true && ✅ test passed} @@ -232,67 +258,151 @@ function ChangeSrc({renderId}) { ) } -function TestCheckBox({name, id, checked, onChange}) { +function TestSuspense({rand2}) { return ( -
  • - -
  • + <> + + + Loading... (Suspense fallback)
    }> + + + + ) } -const getUrlTestCases = () => { - const urlParams = new URLSearchParams(window.location.search) - if (!urlParams.has('test') || !urlParams.get('test')) return [] - const uniqueTests = [...new Set(urlParams.get('test')?.split('-'))] - return uniqueTests - .map((test) => parseInt(test, 10)) - .filter((test) => !isNaN(test)) +function TestSuspenseWontLoad({}) { + return ( + ✅ test passed}> + Loading... (Suspense fallback)}> + + + + ) } +const TestReuseCache = ({renderId}) => { + const src = `https://picsum.photos/200?rand=${renderId}` + const [networkCalls, setNetworkCalls] = useState(0) -const updateUrlState = (activeTests) => { - const url = new URL(window.location.href) - const urlParams = new URLSearchParams(url.searchParams) - const nextState = activeTests.join('-') - if (urlParams.get('test') === nextState) return - urlParams.set('test', nextState) - url.search = urlParams.toString() - window.history.pushState({}, '', url.toString()) + useEffect(() => { + setTimeout(() => { + const entires = performance.getEntriesByName(src) + setNetworkCalls(entires.length) + }, 1000) + }) + + return ( +
    + <>Suspense should reuse cache and only make one network call +
    + {networkCalls < 1 && ❓ test pending} + {networkCalls === 1 && ✅ test passed} + {networkCalls > 1 && ( + + ❌ test failed. If DevTools is open, ensure "Disable Cache" in the + network tab is disabled + + )} +
    +
    Network Calls detected: {networkCalls} (expecting just 1)
    +
    +
    + To test this manually, check the Network Tab in DevTools to ensure the + url + {src} was only called once +
    +
    +
    + + Loading... (Suspense fallback)
    }> + + + + + + ) } -const pushToUrlState = (id, include) => { - const testCases = getUrlTestCases() +function TestHooksAndSuspense({rand3}) { + const HooksSuspenseExample = ({rand}) => { + const {src} = useImage({ + srcList: [ + 'https://www.example.com/foo.png', + `/delay/${rand * 1000}/https://picsum.photos/200`, // will be loaded + ], + }) - let nextTestCases - // console.log('pushToUrlState', id, include, testCases) - if (!include) { - nextTestCases = testCases.filter((testCase) => testCase !== id) - } else { - nextTestCases = [...testCases, id].sort() + return ( +
    + +
    + ) } - // console.log('setting test cases', nextTestCases, nextTestCases.join('-')) - updateUrlState(nextTestCases) + + return ( + + + Loading...}> + + + + ) } -function App() { - const imageOn404 = - 'https://i9.ytimg.com/s_p/OLAK5uy_mwasty2cJpgWIpr61CqWRkHIT7LC62u7s/sddefault.jpg?sqp=CJz5ye8Fir7X7AMGCNKz4dEF&rs=AOn4CLC-JNn9jj-oFw94oM574w36xUL1iQ&v=5a3859d2' - const tmdbImg = - 'https://image.tmdb.org/t/p/w500/kqjL17yufvn9OVLyXYpvtyrFfask.jpg' - const locationRef = useRef(window.location.href) +function TestHooksLegacy({rand4}) { + const HooksLegacyExample = ({rand}) => { + const {src, isLoading, error} = useImage({ + srcList: [ + 'https://www.example.com/non-existant-image.jpg', + `/delay/${rand * 1000}/https://picsum.photos/200`, // will be loaded + ], + useSuspense: false, + }) + + return ( +
    +

    Using hooks Legacy

    + + {isLoading &&
    Loading...
    } + {error &&
    Error! {error.msg}
    } + {src && } + {!isLoading && !error && !src && ( +
    Nothing to show - thats not good!
    + )} +
    + ) + } - // http://i.imgur.com/ozEaj1Z.jpg - const rand1 = randSeconds(1, 8) - const rand2 = randSeconds(2, 10) - const rand3 = randSeconds(2, 10) - const rand4 = randSeconds(2, 10) - const rand5 = randSeconds(2, 10) + return ( + + + + ) +} + +// end test cases + +function App() { const [renderId, setRenderId] = useState(Math.random()) const [swRegistered, setSwRegistered] = useState(false) const [testCases, setTestCases] = useState(getUrlTestCases()) - console.log('testCases', testCases) useLayoutEffect(() => { navigator.serviceWorker.ready.then(() => setSwRegistered(true)) }, []) @@ -306,45 +416,79 @@ function App() { } // run once on mount - console.log('onMount', getUrlTestCases()) updateUrlState(getUrlTestCases()) }, []) - const testIsActive = (id) => testCases.includes(id) + const rand1 = randSeconds(1, 8) + const rand2 = randSeconds(2, 10) + const rand3 = randSeconds(2, 10) + const rand4 = randSeconds(2, 10) + + const testRegistry = [ + { + name: 'Should Show', + id: 1, + Test: TestShouldShow, + props: {rand1}, + }, + { + name: 'Should not show anything', + id: 2, + Test: TestShouldNotShowAnything, + }, + { + name: 'Should show unloader', + id: 3, + Test: ShouldShowUnloader, + }, + { + name: 'Change src', + id: 4, + Test: TestChangeSrc, + props: {renderId}, + }, + { + name: 'Suspense', + id: 5, + Test: TestSuspense, + props: {rand2}, + }, + { + name: 'Suspense wont load', + id: 6, + Test: TestSuspenseWontLoad, + }, + { + name: 'Suspense - reuse cache', + id: 7, + Test: TestReuseCache, + props: {renderId}, + }, + { + name: 'Hooks and Suspense', + id: 8, + Test: TestHooksAndSuspense, + props: {rand3}, + }, + { + name: 'Hooks Legacy', + id: 9, + Test: TestHooksLegacy, + props: {rand4}, + }, + ] + + const testIsActive = (id) => testCases.includes(id) || testCases.length === 0 const testOnClick = (id) => (e) => { e.stopPropagation() - pushToUrlState(id, e.target.checked) + updateTestCases(id, e.target.checked, testRegistry) } if (!swRegistered) return
    Waiting for server...
    return ( <> - +
    @@ -357,110 +501,28 @@ function App() {

    Tests to run:

      - - - - + {testRegistry.map((test) => ( + + ))}
    -
    -

    Should show

    - - Loading...
    } - unloader={
    ❎ test failed
    } - /> -
    -
    -

    Should not show anything

    - ✅ test passed
    } + {testRegistry.map((test) => ( + - -
    -

    Should show unloader

    - Loading...
    } - unloader={
    ✅ test passed
    } - /> - -
    - -
    -
    -

    Suspense

    - - - Loading... (Suspense fallback)
    }> - - - - -
    -

    Suspense wont load

    - ✅ test passed
    }> - Loading... (Suspense fallback)}> - - - - -
    - -
    -
    -
    - -

    using hooks & suspense

    - - Loading...
    }> - - - -
    - -
    - - - -
    + ))}
    From 8977d350f8a7b5361a91192a3750124b96fb4c62 Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 12:09:56 +0200 Subject: [PATCH 3/7] refactor --- dev/app.tsx | 60 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/dev/app.tsx b/dev/app.tsx index ac6ce3b4..440f4fd1 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -86,7 +86,10 @@ function GlobalTimer({until}) { Note that test are delayed by a random amount of time. {!maxTimeReached ? ( -

    Elapsed seconds: {Math.trunc(elapsedTime / 1000)}

    +

    + Elapsed seconds: {Math.trunc(elapsedTime / 1000)}
    + (max time: {until}) +

    ) : ( <>

    Max time elapsed!

    @@ -162,13 +165,13 @@ const updateTestCases = (id, include, testList) => { } // begin test cases -function TestShouldShow({rand1}) { +function TestShouldShow({delay}) { return ( <> - + Loading...} unloader={
    ❎ test failed
    } /> @@ -258,15 +261,15 @@ function TestChangeSrc({renderId}) { ) } -function TestSuspense({rand2}) { +function TestSuspense({delay}) { return ( <> - + Loading... (Suspense fallback)}> @@ -339,7 +342,7 @@ const TestReuseCache = ({renderId}) => { ) } -function TestHooksAndSuspense({rand3}) { +function TestHooksAndSuspense({delay}) { const HooksSuspenseExample = ({rand}) => { const {src} = useImage({ srcList: [ @@ -357,15 +360,15 @@ function TestHooksAndSuspense({rand3}) { return ( - + Loading...}> - + ) } -function TestHooksLegacy({rand4}) { +function TestHooksLegacy({delay}) { const HooksLegacyExample = ({rand}) => { const {src, isLoading, error} = useImage({ srcList: [ @@ -391,7 +394,7 @@ function TestHooksLegacy({rand4}) { return ( - + ) } @@ -402,6 +405,7 @@ function App() { const [renderId, setRenderId] = useState(Math.random()) const [swRegistered, setSwRegistered] = useState(false) const [testCases, setTestCases] = useState(getUrlTestCases()) + let delays: number[] = [] useLayoutEffect(() => { navigator.serviceWorker.ready.then(() => setSwRegistered(true)) @@ -419,17 +423,22 @@ function App() { updateUrlState(getUrlTestCases()) }, []) - const rand1 = randSeconds(1, 8) - const rand2 = randSeconds(2, 10) - const rand3 = randSeconds(2, 10) - const rand4 = randSeconds(2, 10) + const getDelay = (min = 1, max = 10) => { + const rand = randSeconds(1, 8) + delays.push(rand) + return rand + } + + const getMaxDelay = () => { + return delays.reduce((acc, curr) => Math.max(acc, curr), 0) + } const testRegistry = [ { name: 'Should Show', id: 1, Test: TestShouldShow, - props: {rand1}, + props: {delay: getDelay(1, 8)}, }, { name: 'Should not show anything', @@ -451,7 +460,7 @@ function App() { name: 'Suspense', id: 5, Test: TestSuspense, - props: {rand2}, + props: {delay: getDelay(2, 10)}, }, { name: 'Suspense wont load', @@ -468,13 +477,13 @@ function App() { name: 'Hooks and Suspense', id: 8, Test: TestHooksAndSuspense, - props: {rand3}, + props: {delay: getDelay(2, 10)}, }, { name: 'Hooks Legacy', id: 9, Test: TestHooksLegacy, - props: {rand4}, + props: {delay: getDelay(2, 10)}, }, ] @@ -493,8 +502,15 @@ function App() {
    - - + +




    From 76a507c542ccec6c850bc31c89dd52f481652049 Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 17:59:32 +0200 Subject: [PATCH 4/7] global status - half done --- dev/app.tsx | 220 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 170 insertions(+), 50 deletions(-) diff --git a/dev/app.tsx b/dev/app.tsx index 440f4fd1..2662738b 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -4,6 +4,9 @@ import React, { useEffect, useRef, useLayoutEffect, + useMemo, + memo, + forwardRef, } from 'react' import {createRoot} from 'react-dom/client' import {Img, useImage} from '../src/index' @@ -66,7 +69,7 @@ function Timer({delay}) { ) } -function GlobalTimer({until}) { +function GlobalTimer({until, testRegistry, runResults}) { const [startTime] = useState(Date.now()) const [elapsedTime, setElapsedTime] = useState(0) const maxTimeReached = elapsedTime / 1000 - 2 > until @@ -77,13 +80,29 @@ function GlobalTimer({until}) { return () => clearTimeout(timer) }, [elapsedTime]) + const totalTests = testRegistry.length + const passedTests = Object.values(runResults).filter( + (status) => status === 'pass', + ).length + const failedTests = Object.values(runResults).filter( + (status) => status === 'fail', + ).length + let testsStatus + if (passedTests + failedTests !== totalTests) { + testsStatus = 'pending' + } else if (failedTests > 0) { + testsStatus = 'fail' + } else { + testsStatus = 'pass' + } + return (

    React Image visual tests

    Test will load on page load. For a test to pass, one or more images - should show in a green box or the text "✅ test passed" should show. - Note that test are delayed by a random amount of time. + should show in a green box or the text "{}" + should show. Note that test are delayed by a random amount of time.
    {!maxTimeReached ? (

    @@ -97,6 +116,31 @@ function GlobalTimer({until}) {
    )} + + All Tests: + + {failedTests > 0 && ( + <> +
    + Failed Tests: {failedTests}! +
    + + )} +
    +
    + {testRegistry.map((test) => { + return ( + + {test.name}:{' '} + {runResults[test.id] ? ( + + ) : ( + ❓ pending + )} +
    +
    + ) + })}

    ) @@ -112,12 +156,21 @@ function SelectorCheckBox({name, id, checked, onChange}) { ) } -function TestContainer({name, isActive, id, Test, props}) { +function TestContainer({ + name, + isActive, + id, + Test, + props, + runResults, + setRunResults, +}) { if (!isActive) return null return (

    {name}

    - + {/* */} +
    ) } @@ -164,30 +217,84 @@ const updateTestCases = (id, include, testList) => { updateUrlState(nextTestCases) } +function ResultView({status}, ref) { + if (status === 'pending' || !status) return ❓ pending + if (status === 'pass') return ✅ passed + if (status === 'fail') return ❌ failed + return null +} +const Results = forwardRef(ResultView) + // begin test cases -function TestShouldShow({delay}) { +function TestShouldShow({delay, setRunResults}) { + const imgRef = useRef(null) + + useEffect(() => { + setTimeout( + () => { + console.log('finish loading', imgRef.current?.src) + setRunResults(imgRef.current?.src ? 'pass' : 'fail') + }, + (delay + 1) * 1000, + ) + }) + return ( <> Loading...
    } - unloader={
    ❎ test failed
    } + loader={} + unloader={ +
    + +
    + } /> ) } -function TestShouldNotShowAnything({}) { +function TestShouldNotShowAnything({setRunResults}) { + const imgRef = useRef(null) + const unloaderRef = useRef(null) + window.x = unloaderRef + useEffect(() => { + setTimeout(() => { + if (unloaderRef.current?.innerText) { + setRunResults('pass') + } else { + setRunResults('fail') + } + }, 100) + }) + return ( - ✅ test passed
    } /> + } + /> ) } -function ShouldShowUnloader({}) { +function ShouldShowUnloader({setRunResults}) { + const imgRef = useRef(null) + + useEffect(() => { + if (!imgRef.current || !imgRef.current.src) { + setRunResults('pass') + } else { + setRunResults('fail') + } + }) + return ( Loading...
    } @@ -196,58 +303,60 @@ function ShouldShowUnloader({}) { ) } -function TestChangeSrc({renderId}) { +function TestChangeSrc({setRunResults}) { const getSrc = () => { const rand = randSeconds(500, 900) - return `https://picsum.photos/200?rand=${rand}` + return `https://picsum.photos/200?test=TestChangeSrc&rand=${rand}` } const [src, setSrc] = useState([getSrc()]) - const [loadedSecondSource, setLoadedSecondSource] = useState( - null, - ) + const [loaded, setLoaded] = useState(null) const imgRef = useRef(null) useEffect(() => { - if (src.length < 2) return - - let id = setInterval( - () => setLoadedSecondSource(imgRef.current?.src === src[1]), - 250, - ) - return () => clearInterval(id) - }, [renderId, src]) + const onFirstLoad = () => setSrc((prev) => [...prev, getSrc()]) + const onSecondLoad = () => { + setLoaded(true) + setRunResults('pass') + } - useEffect(() => { - // switch sources after 1 second - setTimeout(() => setSrc((prev) => [...prev, getSrc()]), 1000) - }, [renderId]) + if (src.length === 1) { + if (imgRef.current?.src === src[1]) { + onFirstLoad() + } else { + imgRef.current?.addEventListener('load', onFirstLoad) + } + } else { + if (imgRef.current?.src === src[2]) { + onSecondLoad() + } else { + imgRef.current?.addEventListener('load', onSecondLoad) + } + } + }, [src]) - // on rerender, reset the src list - useEffect(() => { - setSrc(() => [getSrc()]) - setLoadedSecondSource(null) - }, [renderId]) + let testResults + if (loaded === null) testResults = 'pending' + if (loaded === true) testResults = 'pass' + if (loaded === false) testResults = 'fail' return ( <> -
    - {loadedSecondSource === null && ❓ test pending} - {loadedSecondSource === true && ✅ test passed} - {loadedSecondSource === false && ❌ test failed} -
    + +
    Src list: {src.map((url, index) => { return ( -
    +
    {index + 1}. {url}
    ) })}
    - This test will load an image and then switch sources after 1 second. It + This test will load an image and then switch to a different sources. It should then rerender with the new source. To manually confirm, ensure - the loaded image's source is the second item in the Src list + the loaded image's source is the second item in the src{' '} + list

    (getUrlTestCases()) + // const [runResults, setRunResults] = useState({}) let delays: number[] = [] + let runResults = {} useLayoutEffect(() => { navigator.serviceWorker.ready.then(() => setSwRegistered(true)) @@ -433,6 +544,12 @@ function App() { return delays.reduce((acc, curr) => Math.max(acc, curr), 0) } + const resetTests = () => { + delays = [] + runResults = [] + setRenderId(Math.random()) + } + const testRegistry = [ { name: 'Should Show', @@ -491,6 +608,7 @@ function App() { const testOnClick = (id) => (e) => { e.stopPropagation() updateTestCases(id, e.target.checked, testRegistry) + resetTests() } if (!swRegistered) return
    Waiting for server...
    @@ -502,15 +620,15 @@ function App() {
    - - + + testIsActive(test.id), + )} + runResults={runResults} + /> +




    @@ -536,6 +654,8 @@ function App() { (runResults[test.id] = status)} {...test} /> ))} From 3a05e7dbc6eaec17e18a5bfe44723cd54c60d50d Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 19:52:39 +0200 Subject: [PATCH 5/7] test runner results --- dev/app.tsx | 139 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 101 insertions(+), 38 deletions(-) diff --git a/dev/app.tsx b/dev/app.tsx index 2662738b..74c91d0a 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -72,7 +72,7 @@ function Timer({delay}) { function GlobalTimer({until, testRegistry, runResults}) { const [startTime] = useState(Date.now()) const [elapsedTime, setElapsedTime] = useState(0) - const maxTimeReached = elapsedTime / 1000 - 2 > until + const maxTimeReached = elapsedTime / 1000 > until + 3 useEffect(() => { if (maxTimeReached) return @@ -232,7 +232,6 @@ function TestShouldShow({delay, setRunResults}) { useEffect(() => { setTimeout( () => { - console.log('finish loading', imgRef.current?.src) setRunResults(imgRef.current?.src ? 'pass' : 'fail') }, (delay + 1) * 1000, @@ -260,14 +259,10 @@ function TestShouldShow({delay, setRunResults}) { function TestShouldNotShowAnything({setRunResults}) { const imgRef = useRef(null) const unloaderRef = useRef(null) - window.x = unloaderRef + useEffect(() => { setTimeout(() => { - if (unloaderRef.current?.innerText) { - setRunResults('pass') - } else { - setRunResults('fail') - } + setRunResults(unloaderRef.current?.innerText ? 'pass' : 'fail') }, 100) }) @@ -282,23 +277,22 @@ function TestShouldNotShowAnything({setRunResults}) { } function ShouldShowUnloader({setRunResults}) { - const imgRef = useRef(null) + const unloaderRef = useRef(null) useEffect(() => { - if (!imgRef.current || !imgRef.current.src) { - setRunResults('pass') - } else { - setRunResults('fail') - } + setTimeout(() => { + setRunResults( + unloaderRef.current && unloaderRef.current.innerText ? 'pass' : 'fail', + ) + }, 100) }) return ( Loading...
    } - unloader={
    ✅ test passed
    } + unloader={} /> ) } @@ -370,13 +364,33 @@ function TestChangeSrc({setRunResults}) { ) } -function TestSuspense({delay}) { +function TestSuspense({delay, setRunResults}) { + const imgRef = useRef(null) + + useEffect(() => { + setTimeout( + () => { + const onLoad = () => { + setRunResults(imgRef.current?.src ? 'pass' : 'fail') + } + + if (imgRef.current?.src) { + onLoad() + } else { + imgRef.current?.addEventListener('load', onLoad) + } + }, + (delay + 1) * 1000, + ) + }) + return ( <> Loading... (Suspense fallback)
    }> (null) + + useEffect(() => { + setTimeout(() => { + setRunResults(resRef.current?.innerText ? 'pass' : 'fail') + }, 100) + }) + return ( - ✅ test passed
    }> + }> Loading... (Suspense fallback)
    }> ) } -const TestReuseCache = ({renderId}) => { - const src = `https://picsum.photos/200?rand=${renderId}` + +const TestReuseCache = ({setRunResults}) => { + const imgRef = useRef(null) + const src = `https://picsum.photos/200?rand=${randSeconds(500, 900)}` const [networkCalls, setNetworkCalls] = useState(0) useEffect(() => { setTimeout(() => { const entires = performance.getEntriesByName(src) setNetworkCalls(entires.length) + + if (imgRef.current?.src === src) { + setRunResults('pass') + } }, 1000) }) + let testResults + if (networkCalls === 0) testResults = 'pending' + if (networkCalls === 1) testResults = 'pass' + if (networkCalls > 1) testResults = 'fail' + return (
    <>Suspense should reuse cache and only make one network call
    - {networkCalls < 1 && ❓ test pending} - {networkCalls === 1 && ✅ test passed} - {networkCalls > 1 && ( + + {testResults === 'fail' && ( - ❌ test failed. If DevTools is open, ensure "Disable Cache" in the - network tab is disabled + If DevTools is open, ensure "Disable Cache" in the network tab is + disabled )}
    @@ -441,6 +473,7 @@ const TestReuseCache = ({renderId}) => { useSuspense={true} /> { ) } -function TestHooksAndSuspense({delay}) { +function TestHooksAndSuspense({delay, setRunResults}) { + const imgRef = useRef(null) + const HooksSuspenseExample = ({rand}) => { + useEffect(() => { + setTimeout( + () => { + const onLoad = () => { + setRunResults(imgRef.current?.src ? 'pass' : 'fail') + } + if (imgRef.current?.src) { + onLoad() + } else { + imgRef.current?.addEventListener('load', onLoad) + } + }, + (delay + 1) * 1000, + ) + }) + const {src} = useImage({ srcList: [ 'https://www.example.com/foo.png', @@ -462,7 +513,7 @@ function TestHooksAndSuspense({delay}) { return (
    - +
    ) } @@ -477,23 +528,35 @@ function TestHooksAndSuspense({delay}) { ) } -function TestHooksLegacy({delay}) { +function TestHooksLegacy({delay, setRunResults}) { + const imgRef = useRef(null) + + const {src, isLoading, error} = useImage({ + srcList: [ + 'https://www.example.com/non-existant-image.jpg', + `/delay/${delay * 1000}/https://picsum.photos/200`, // will be loaded + ], + useSuspense: false, + }) + const HooksLegacyExample = ({rand}) => { - const {src, isLoading, error} = useImage({ - srcList: [ - 'https://www.example.com/non-existant-image.jpg', - `/delay/${rand * 1000}/https://picsum.photos/200`, // will be loaded - ], - useSuspense: false, - }) + useEffect(() => { + const onLoad = () => { + setRunResults(imgRef.current?.src ? 'pass' : 'fail') + } + if (imgRef.current?.src) { + onLoad() + } else { + imgRef.current?.addEventListener('load', onLoad) + } + }, [src]) return (
    -

    Using hooks Legacy

    {isLoading &&
    Loading...
    } {error &&
    Error! {error.msg}
    } - {src && } + {src && } {!isLoading && !error && !src && (
    Nothing to show - thats not good!
    )} From 10144bc6b4e52a1c9e8b673b1a0d349f13e1f7a6 Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 19:54:31 +0200 Subject: [PATCH 6/7] bump --- dev/app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/app.tsx b/dev/app.tsx index 74c91d0a..365a9175 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -161,7 +161,7 @@ function TestContainer({ isActive, id, Test, - props, + props = {}, runResults, setRunResults, }) { From 6b5c5760226ac21bac4f4b8c24dfbc99c526c990 Mon Sep 17 00:00:00 2001 From: Moshe Brevda Date: Sun, 12 Nov 2023 19:57:43 +0200 Subject: [PATCH 7/7] bump --- build.js | 4 ++-- dev/app.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.js b/build.js index 66ba0927..a36e800f 100644 --- a/build.js +++ b/build.js @@ -15,7 +15,7 @@ const buildOpts = { format: 'esm', sourcemap: false, minify: true, - jsxDev: process.env.NODE_ENV === 'development', + jsxDev: false, jsx: 'automatic', } @@ -26,7 +26,7 @@ const devBuildOpts = { outdir: distOutdir, format: 'esm', sourcemap: true, - minify: process.env.NODE_ENV !== 'development', + minify: true, jsxDev: true, jsx: 'automatic', loader: {'.html': 'copy'}, diff --git a/dev/app.tsx b/dev/app.tsx index 365a9175..3949f193 100644 --- a/dev/app.tsx +++ b/dev/app.tsx @@ -290,7 +290,7 @@ function ShouldShowUnloader({setRunResults}) { return ( Loading...
    } unloader={} /> @@ -415,7 +415,7 @@ function TestSuspenseWontLoad({setRunResults}) { Loading... (Suspense fallback)
    }>