-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: This implements a basic version of NodeList that's close to the spec but diverges in some things (e.g.: methods could be called with an instance created through `Object.create`, etc.). This will be used soon to implement `ReadOnlyNode.childNodes` (behind a flag). See: react-native-community/discussions-and-proposals#607 Changelog: [internal] Reviewed By: yungsters Differential Revision: D44055911 fbshipit-source-id: 10b435b06ea6f75eaead85f01c2703e05bb3bd37
- Loading branch information
1 parent
e4d83a1
commit c4b84ba
Showing
4 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
packages/react-native/Libraries/DOM/OldStyleCollections/NodeList.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @format | ||
* @flow strict | ||
*/ | ||
|
||
// flowlint unsafe-getters-setters:off | ||
|
||
import type {ArrayLike} from './ArrayLikeUtils'; | ||
|
||
import { | ||
createEntriesIterator, | ||
createKeyIterator, | ||
createValueIterator, | ||
} from './ArrayLikeUtils'; | ||
|
||
// IMPORTANT: The Flow type definition for this module is defined in `NodeList.js.flow` | ||
// because Flow only supports indexers in classes in declaration files. | ||
|
||
// $FlowIssue[prop-missing] Flow doesn't understand [Symbol.iterator]() {} and thinks this class doesn't implement the Iterable<T> interface. | ||
export default class NodeList<T> implements Iterable<T>, ArrayLike<T> { | ||
_length: number; | ||
|
||
/** | ||
* Use `createNodeList` to create instances of this class. | ||
* | ||
* @private This is not defined in the declaration file, so users will not see | ||
* the signature of the constructor. | ||
*/ | ||
constructor(elements: $ReadOnlyArray<T>) { | ||
for (let i = 0; i < elements.length; i++) { | ||
Object.defineProperty(this, i, { | ||
value: elements[i], | ||
writable: false, | ||
}); | ||
} | ||
this._length = elements.length; | ||
} | ||
|
||
get length(): number { | ||
return this._length; | ||
} | ||
|
||
item(index: number): T | null { | ||
if (index < 0 || index >= this._length) { | ||
return null; | ||
} | ||
|
||
// assigning to the interface allows us to access the indexer property in a | ||
// type-safe way. | ||
// eslint-disable-next-line consistent-this | ||
const arrayLike: ArrayLike<T> = this; | ||
return arrayLike[index]; | ||
} | ||
|
||
entries(): Iterator<[number, T]> { | ||
return createEntriesIterator(this); | ||
} | ||
|
||
forEach<ThisType>( | ||
callbackFn: (value: T, index: number, array: NodeList<T>) => mixed, | ||
thisArg?: ThisType, | ||
): void { | ||
// assigning to the interface allows us to access the indexer property in a | ||
// type-safe way. | ||
// eslint-disable-next-line consistent-this | ||
const arrayLike: ArrayLike<T> = this; | ||
|
||
for (let index = 0; index < this._length; index++) { | ||
if (thisArg == null) { | ||
callbackFn(arrayLike[index], index, this); | ||
} else { | ||
callbackFn.call(thisArg, arrayLike[index], index, this); | ||
} | ||
} | ||
} | ||
|
||
keys(): Iterator<number> { | ||
return createKeyIterator(this); | ||
} | ||
|
||
values(): Iterator<T> { | ||
return createValueIterator(this); | ||
} | ||
|
||
// $FlowIssue[unsupported-syntax] Flow does not support computed properties in classes. | ||
[Symbol.iterator](): Iterator<T> { | ||
return createValueIterator(this); | ||
} | ||
} | ||
|
||
/** | ||
* This is an internal method to create instances of `NodeList`, | ||
* which avoids leaking its constructor to end users. | ||
* We can do that because the external definition of `NodeList` lives in | ||
* `NodeList.js.flow`, not here. | ||
*/ | ||
export function createNodeList<T>(elements: $ReadOnlyArray<T>): NodeList<T> { | ||
return new NodeList(elements); | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/react-native/Libraries/DOM/OldStyleCollections/NodeList.js.flow
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @format | ||
* @flow strict | ||
*/ | ||
|
||
import type {ArrayLike} from './ArrayLikeUtils'; | ||
|
||
declare export default class NodeList<+T> implements Iterable<T>, ArrayLike<T> { | ||
// This property should've been read-only as well, but Flow doesn't handle | ||
// read-only indexers correctly (thinks reads are writes and fails). | ||
[index: number]: T; | ||
+length: number; | ||
item(index: number): T | null; | ||
entries(): Iterator<[number, T]>; | ||
forEach<ThisType>( | ||
callbackFn: (value: T, index: number, array: NodeList<T>) => mixed, | ||
thisArg?: ThisType, | ||
): void; | ||
keys(): Iterator<number>; | ||
values(): Iterator<T>; | ||
@@iterator(): Iterator<T>; | ||
} | ||
|
||
declare export function createNodeList<T>( | ||
elements: $ReadOnlyArray<T>, | ||
): NodeList<T>; |
161 changes: 161 additions & 0 deletions
161
packages/react-native/Libraries/DOM/OldStyleCollections/__tests__/NodeList-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow strict-local | ||
* @format | ||
* @oncall react_native | ||
*/ | ||
|
||
import {createNodeList} from '../NodeList'; | ||
|
||
describe('NodeList', () => { | ||
it('provides an array-like interface', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
expect(collection[0]).toBe('a'); | ||
expect(collection[1]).toBe('b'); | ||
expect(collection[2]).toBe('c'); | ||
expect(collection[3]).toBe(undefined); | ||
expect(collection.length).toBe(3); | ||
}); | ||
|
||
it('provides indexed access through the item method', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
expect(collection.item(0)).toBe('a'); | ||
expect(collection.item(1)).toBe('b'); | ||
expect(collection.item(2)).toBe('c'); | ||
expect(collection.item(3)).toBe(null); | ||
}); | ||
|
||
it('is immutable (loose mode)', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
collection[0] = 'replacement'; | ||
expect(collection[0]).toBe('a'); | ||
|
||
// $FlowExpectedError[cannot-write] | ||
collection.length = 100; | ||
expect(collection.length).toBe(3); | ||
}); | ||
|
||
it('is immutable (strict mode)', () => { | ||
'use strict'; | ||
|
||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
expect(() => { | ||
collection[0] = 'replacement'; | ||
}).toThrow(TypeError); | ||
expect(collection[0]).toBe('a'); | ||
|
||
expect(() => { | ||
// $FlowExpectedError[cannot-write] | ||
collection.length = 100; | ||
}).toThrow(TypeError); | ||
expect(collection.length).toBe(3); | ||
}); | ||
|
||
it('can be converted to an array through common methods', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
expect(Array.from(collection)).toEqual(['a', 'b', 'c']); | ||
expect([...collection]).toEqual(['a', 'b', 'c']); | ||
}); | ||
|
||
it('can be traversed with for-of', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
let i = 0; | ||
for (const value of collection) { | ||
expect(value).toBe(collection[i]); | ||
i++; | ||
} | ||
}); | ||
|
||
describe('keys()', () => { | ||
it('returns an iterator for keys', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
const keys = collection.keys(); | ||
expect(keys.next()).toEqual({value: 0, done: false}); | ||
expect(keys.next()).toEqual({value: 1, done: false}); | ||
expect(keys.next()).toEqual({value: 2, done: false}); | ||
expect(keys.next()).toEqual({done: true}); | ||
|
||
let i = 0; | ||
for (const key of collection.keys()) { | ||
expect(key).toBe(i); | ||
i++; | ||
} | ||
}); | ||
}); | ||
|
||
describe('values()', () => { | ||
it('returns an iterator for values', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
const values = collection.values(); | ||
expect(values.next()).toEqual({value: 'a', done: false}); | ||
expect(values.next()).toEqual({value: 'b', done: false}); | ||
expect(values.next()).toEqual({value: 'c', done: false}); | ||
expect(values.next()).toEqual({done: true}); | ||
|
||
let i = 0; | ||
for (const value of collection.values()) { | ||
expect(value).toBe(collection[i]); | ||
i++; | ||
} | ||
}); | ||
}); | ||
|
||
describe('entries()', () => { | ||
it('returns an iterator for entries', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
const entries = collection.entries(); | ||
expect(entries.next()).toEqual({value: [0, 'a'], done: false}); | ||
expect(entries.next()).toEqual({value: [1, 'b'], done: false}); | ||
expect(entries.next()).toEqual({value: [2, 'c'], done: false}); | ||
expect(entries.next()).toEqual({done: true}); | ||
|
||
let i = 0; | ||
for (const entry of collection.entries()) { | ||
expect(entry).toEqual([i, collection[i]]); | ||
i++; | ||
} | ||
}); | ||
}); | ||
|
||
describe('forEach()', () => { | ||
it('iterates over the elements like array.forEach (implicit `this`)', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
let i = 0; | ||
collection.forEach(function (this: mixed, value, index, list) { | ||
expect(value).toBe(collection[i]); | ||
expect(index).toBe(i); | ||
expect(list).toBe(collection); | ||
expect(this).toBe(window); | ||
i++; | ||
}); | ||
}); | ||
|
||
it('iterates over the elements like array.forEach (explicit `this`)', () => { | ||
const collection = createNodeList(['a', 'b', 'c']); | ||
|
||
let i = 0; | ||
const explicitThis = {id: 'foo'}; | ||
collection.forEach(function (this: mixed, value, index, list) { | ||
expect(value).toBe(collection[i]); | ||
expect(index).toBe(i); | ||
expect(list).toBe(collection); | ||
expect(this).toBe(explicitThis); | ||
i++; | ||
}, explicitThis); | ||
}); | ||
}); | ||
}); |