core-api: add internal attachNodeData and getComponentData helpers
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: blam <ben@blam.sh>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { attachComponentData, getComponentData } from './componentData';
|
||||
|
||||
describe('elementData', () => {
|
||||
it('should attach a single piece of data', () => {
|
||||
const data = { foo: 'bar' };
|
||||
const Component = () => null;
|
||||
attachComponentData(Component, 'my-data', data);
|
||||
|
||||
const element = <Component />;
|
||||
expect(getComponentData(element, 'my-data')).toBe(data);
|
||||
});
|
||||
|
||||
it('should attach several distinct pieces of data', () => {
|
||||
const data1 = { foo: 'bar' };
|
||||
const data2 = { test: 'value' };
|
||||
const Component = () => null;
|
||||
attachComponentData(Component, 'my-data', data1);
|
||||
attachComponentData(Component, 'second', data2);
|
||||
|
||||
const element = <Component />;
|
||||
expect(getComponentData(element, 'my-data')).toBe(data1);
|
||||
expect(getComponentData(element, 'second')).toBe(data2);
|
||||
});
|
||||
|
||||
it('returns undefined for missing data', () => {
|
||||
const data = { foo: 'bar' };
|
||||
const Component1 = () => null;
|
||||
const Component2 = () => null;
|
||||
attachComponentData(Component2, 'my-data', data);
|
||||
|
||||
const element1 = <Component1 />;
|
||||
const element2 = <Component2 />;
|
||||
expect(getComponentData(element1, 'missing')).toBeUndefined();
|
||||
expect(getComponentData(element2, 'missing')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw when attempting to overwrite data', () => {
|
||||
const data = { foo: 'bar' };
|
||||
const MyComponent = () => null;
|
||||
attachComponentData(MyComponent, 'my-data', data);
|
||||
expect(() => attachComponentData(MyComponent, 'my-data', data)).toThrow(
|
||||
'Attempted to attach duplicate data "my-data" to component "MyComponent"',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentType, ReactNode } from 'react';
|
||||
|
||||
const DATA_KEY = Symbol('backstage-component-data');
|
||||
|
||||
type DataContainer = {
|
||||
map: Map<string, unknown>;
|
||||
};
|
||||
|
||||
type ComponentWithData<P> = ComponentType<P> & {
|
||||
[DATA_KEY]?: DataContainer;
|
||||
};
|
||||
|
||||
type ReactNodeWithData = ReactNode & {
|
||||
type?: { [DATA_KEY]?: DataContainer };
|
||||
};
|
||||
|
||||
export function attachComponentData<P>(
|
||||
component: ComponentType<P>,
|
||||
type: string,
|
||||
data: unknown,
|
||||
) {
|
||||
const dataComponent = component as ComponentWithData<P>;
|
||||
|
||||
let container = dataComponent[DATA_KEY];
|
||||
if (!container) {
|
||||
container = dataComponent[DATA_KEY] = { map: new Map() };
|
||||
}
|
||||
|
||||
if (container.map.has(type)) {
|
||||
const name = component.displayName || component.name;
|
||||
throw new Error(
|
||||
`Attempted to attach duplicate data "${type}" to component "${name}"`,
|
||||
);
|
||||
}
|
||||
|
||||
container.map.set(type, data);
|
||||
}
|
||||
|
||||
export function getComponentData<T>(
|
||||
node: ReactNode,
|
||||
type: string,
|
||||
): T | undefined {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const container = (node as ReactNodeWithData).type?.[DATA_KEY];
|
||||
if (!container) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return container.map.get(type) as T | undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user