Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: bind removeEventListener #1596

Merged
merged 6 commits into from
Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
testEnvironment: 'jsdom',
testRegex: '/test/.*\\.test\\.tsx$',
testRegex: '/test/.*\\.test\\.tsx?$',
modulePathIgnorePatterns: ['<rootDir>/examples/'],
setupFilesAfterEnv: ['<rootDir>/scripts/jest-setup.ts'],
transform: {
Expand Down
2 changes: 1 addition & 1 deletion scripts/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/env.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huozhi Could you tell me why do we need to use a function? I'm curious to learn.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for both source code and tests, if we make it a function, then in the test case we could switch environment between node and jsdom so that the evaluation is different when runtime changes.

I didn't find a good way to keep it a pre-evaluated variable while switching runtime. If you have any good idea to keep it simple that would be great 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add a comment in the source code ;)

Copy link
Contributor

@nguyenyou nguyenyou Dec 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huozhi Thank you 🙏 I think this is the simplest way to achieve it 👍

export const hasDocument = () => typeof document != STR_UNDEFINED
17 changes: 10 additions & 7 deletions src/utils/web-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand Down
10 changes: 5 additions & 5 deletions test/unit.test.tsx → test/unit/utils.test.tsx
Original file line number Diff line number Diff line change
@@ -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 }
Expand Down
98 changes: 98 additions & 0 deletions test/unit/web-preset.test.ts
Original file line number Diff line number Diff line change
@@ -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')
2 changes: 1 addition & 1 deletion test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ describe('useSWR - local mutation', () => {
}

startMutation()
}, [])
}, [mutate])

loggedData.push(data)
return null
Expand Down