diff --git a/jest.config.js b/jest.config.js index 891fd7628..f98a02e87 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ module.exports = { testEnvironment: 'jsdom', - testRegex: '/test/.*\\.test\\.tsx$', + testRegex: '/test/.*\\.test\\.tsx?$', modulePathIgnorePatterns: ['/examples/'], setupFilesAfterEnv: ['/scripts/jest-setup.ts'], transform: { diff --git a/scripts/watch.js b/scripts/watch.js index 3f7b5164c..2ef46b97d 100644 --- a/scripts/watch.js +++ b/scripts/watch.js @@ -5,7 +5,7 @@ const bundle = require('bunchee').bundle const childProcess = require('child_process') const args = process.argv -const target = args[2] +const target = args[2] || 'core' const entryMap = { core: { diff --git a/src/utils/env.ts b/src/utils/env.ts index c4e757ae5..e59f75c06 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -1,11 +1,11 @@ import { useEffect, useLayoutEffect } from 'react' import { hasWindow } from './helper' -export const IS_SERVER = !hasWindow || 'Deno' in window +export const IS_SERVER = !hasWindow() || 'Deno' in window // Polyfill requestAnimationFrame export const rAF = - (hasWindow && window['requestAnimationFrame']) || + (hasWindow() && window['requestAnimationFrame']) || ((f: (...args: any[]) => void) => setTimeout(f, 1)) // React currently throws a warning when using useLayoutEffect on the server. diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 89fdf6129..f153ef448 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -13,5 +13,5 @@ export const isFunction = (v: any): v is Function => typeof v == 'function' export const mergeObjects = (a: any, b: any) => OBJECT.assign({}, a, b) const STR_UNDEFINED = 'undefined' -export const hasWindow = typeof window != STR_UNDEFINED -export const hasDocument = typeof document != STR_UNDEFINED +export const hasWindow = () => typeof window != STR_UNDEFINED +export const hasDocument = () => typeof document != STR_UNDEFINED diff --git a/src/utils/web-preset.ts b/src/utils/web-preset.ts index 56d346717..6fd71c0f2 100644 --- a/src/utils/web-preset.ts +++ b/src/utils/web-preset.ts @@ -11,22 +11,25 @@ import { isUndefined, noop, hasWindow, hasDocument } from './helper' let online = true const isOnline = () => online +const hasWin = hasWindow() +const hasDoc = hasDocument() + // For node and React Native, `add/removeEventListener` doesn't exist on window. const onWindowEvent = - hasWindow && window.addEventListener + hasWin && window.addEventListener ? window.addEventListener.bind(window) : noop -const onDocumentEvent = hasDocument - ? document.addEventListener.bind(document) - : noop +const onDocumentEvent = hasDoc ? document.addEventListener.bind(document) : noop const offWindowEvent = - hasWindow && window.removeEventListener ? window.removeEventListener : noop -const offDocumentEvent = hasDocument + hasWin && window.removeEventListener + ? window.removeEventListener.bind(window) + : noop +const offDocumentEvent = hasDoc ? document.removeEventListener.bind(document) : noop const isVisible = () => { - const visibilityState = hasDocument && document.visibilityState + const visibilityState = hasDoc && document.visibilityState if (!isUndefined(visibilityState)) { return visibilityState !== 'hidden' } diff --git a/test/unit.test.tsx b/test/unit/utils.test.tsx similarity index 94% rename from test/unit.test.tsx rename to test/unit/utils.test.tsx index 12cd21d4b..0321d26ce 100644 --- a/test/unit.test.tsx +++ b/test/unit/utils.test.tsx @@ -1,9 +1,9 @@ -import { normalize } from '../src/utils/normalize-args' -import { stableHash as hash } from '../src/utils/hash' -import { serialize } from '../src/utils/serialize' -import { mergeConfigs } from '../src/utils/merge-config' +import { normalize } from '../../src/utils/normalize-args' +import { stableHash as hash } from '../../src/utils/hash' +import { serialize } from '../../src/utils/serialize' +import { mergeConfigs } from '../../src/utils/merge-config' -describe('Unit tests', () => { +describe('Utils', () => { it('should normalize arguments correctly', async () => { const fetcher = () => {} const opts = { revalidateOnFocus: false } diff --git a/test/unit/web-preset.test.ts b/test/unit/web-preset.test.ts new file mode 100644 index 000000000..c7d1bc22f --- /dev/null +++ b/test/unit/web-preset.test.ts @@ -0,0 +1,98 @@ +import { EventEmitter } from 'events' + +const FOCUS_EVENT = 'focus' +const VISIBILITYCHANGE_EVENT = 'visibilitychange' + +function createEventTarget() { + EventEmitter.prototype['addEventListener'] = EventEmitter.prototype.on + EventEmitter.prototype['removeEventListener'] = EventEmitter.prototype.off + const target = new EventEmitter() + + return target +} + +function runTests(propertyName) { + let webPreset + let initFocus + const eventName = + propertyName === 'window' ? FOCUS_EVENT : VISIBILITYCHANGE_EVENT + + describe(`Web Preset ${propertyName}`, () => { + const globalSpy = { + window: undefined, + document: undefined + } + + beforeEach(() => { + globalSpy.window = jest.spyOn(global, 'window', 'get') + globalSpy.document = jest.spyOn(global, 'document', 'get') + + jest.resetModules() + }) + + afterEach(() => { + globalSpy.window.mockClear() + globalSpy.document.mockClear() + }) + + it(`should trigger listener when ${propertyName} has browser APIs`, async () => { + const target = createEventTarget() + if (propertyName === 'window') { + globalSpy.window.mockImplementation(() => target) + globalSpy.document.mockImplementation(() => undefined) + } else if (propertyName === 'document') { + globalSpy.window.mockImplementation(() => undefined) + globalSpy.document.mockImplementation(() => target) + } + + webPreset = require('../../src/utils/web-preset') + initFocus = webPreset.defaultConfigOptions.initFocus + + const fn = jest.fn() + const release = initFocus(fn) as () => void + + target.emit(eventName) + expect(fn).toBeCalledTimes(1) + + release() + target.emit(eventName) + expect(fn).toBeCalledTimes(1) + }) + + it(`should not trigger listener when ${propertyName} is falsy`, async () => { + if (propertyName === 'window') { + // window exists but without event APIs + globalSpy.window.mockImplementation(() => ({ + emit: createEventTarget().emit + })) + globalSpy.document.mockImplementation(() => undefined) + } else if (propertyName === 'document') { + globalSpy.window.mockImplementation(() => undefined) + globalSpy.document.mockImplementation(() => undefined) + } + + webPreset = require('../../src/utils/web-preset') + initFocus = webPreset.defaultConfigOptions.initFocus + + const fn = jest.fn() + const release = initFocus(fn) as () => void + const target = global[propertyName] + + // TODO: target?.emit?() breaks prettier, fix prettier format + if (target && target.emit) { + target.emit(eventName) + } + + expect(fn).toBeCalledTimes(0) + + release() + if (target && target.emit) { + target.emit(eventName) + } + expect(fn).toBeCalledTimes(0) + }) + }) +} + +runTests('window') +runTests('document') diff --git a/test/use-swr-local-mutation.test.tsx b/test/use-swr-local-mutation.test.tsx index 302649da8..81b5ce419 100644 --- a/test/use-swr-local-mutation.test.tsx +++ b/test/use-swr-local-mutation.test.tsx @@ -903,7 +903,7 @@ describe('useSWR - local mutation', () => { } startMutation() - }, []) + }, [mutate]) loggedData.push(data) return null