mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement basic version of HTMLCollection
Summary: This implements a basic version of HTMLCollection 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 `ReadOnlyElement.children` (behind a flag). See: https://github.com/react-native-community/discussions-and-proposals/pull/607 Changelog: [internal] Reviewed By: yungsters Differential Revision: D44055912 fbshipit-source-id: 37bcd7c12916b95a258e6b2e5717a642f478abdf
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4ad5fe3c21
commit
e4d83a14f5
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* This definition is different from the current built-in type `$ArrayLike`
|
||||
* provided by Flow, in that this is an interface and that one is an object.
|
||||
*
|
||||
* The difference is important because, when using objects, Flow thinks
|
||||
* a `length` property would be copied over when using the spread operator,
|
||||
* which is incorrect.
|
||||
*/
|
||||
export interface ArrayLike<T> extends Iterable<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).
|
||||
[indexer: number]: T;
|
||||
+length: number;
|
||||
}
|
||||
|
||||
export function* createValueIterator<T>(arrayLike: ArrayLike<T>): Iterator<T> {
|
||||
for (let i = 0; i < arrayLike.length; i++) {
|
||||
yield arrayLike[i];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 {createValueIterator} from './ArrayLikeUtils';
|
||||
|
||||
// IMPORTANT: The type definition for this module is defined in `HTMLCollection.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 HTMLCollection<T> implements Iterable<T>, ArrayLike<T> {
|
||||
_length: number;
|
||||
|
||||
/**
|
||||
* Use `createHTMLCollection` 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],
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused in React Native.
|
||||
*/
|
||||
namedItem(name: string): T | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// $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 `HTMLCollection`,
|
||||
* which avoids leaking its constructor to end users.
|
||||
* We can do that because the external definition of `HTMLCollection` lives in
|
||||
* `HTMLCollection.js.flow`, not here.
|
||||
*/
|
||||
export function createHTMLCollection<T>(
|
||||
elements: $ReadOnlyArray<T>,
|
||||
): HTMLCollection<T> {
|
||||
return new HTMLCollection(elements);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 HTMLCollection<+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;
|
||||
namedItem(name: string): T | null;
|
||||
@@iterator(): Iterator<T>;
|
||||
}
|
||||
|
||||
declare export function createHTMLCollection<T>(
|
||||
elements: $ReadOnlyArray<T>,
|
||||
): HTMLCollection<T>;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 {createHTMLCollection} from '../HTMLCollection';
|
||||
|
||||
describe('HTMLCollection', () => {
|
||||
it('provides an array-like interface', () => {
|
||||
const collection = createHTMLCollection(['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('is immutable (loose mode)', () => {
|
||||
const collection = createHTMLCollection(['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 = createHTMLCollection(['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 = createHTMLCollection(['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 = createHTMLCollection(['a', 'b', 'c']);
|
||||
|
||||
let i = 0;
|
||||
for (const value of collection) {
|
||||
expect(value).toBe(collection[i]);
|
||||
i++;
|
||||
}
|
||||
});
|
||||
|
||||
describe('item()', () => {
|
||||
it('returns elements at the specified position, or null', () => {
|
||||
const collection = createHTMLCollection(['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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user