Skip to content

Commit

Permalink
Merge fa0ce73 into 325c567
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesplease committed Jun 17, 2021
2 parents 325c567 + fa0ce73 commit 598377c
Show file tree
Hide file tree
Showing 32 changed files with 2,983 additions and 91 deletions.
22 changes: 22 additions & 0 deletions jest-setup.js
@@ -0,0 +1,22 @@
import '@testing-library/jest-dom';
import * as warning from './src/utils/warning';

beforeEach(() => {
if (console.error.mockRestore) {
console.error.mockRestore();
}

if (console.warn.mockRestore) {
console.warn.mockRestore();
}

if (warning.warning.mockRestore) {
warning.warning.mockRestore();
}

jest.spyOn(console, 'error').mockImplementation(() => {});
jest.spyOn(console, 'warn').mockImplementation(() => {});
jest.spyOn(warning, 'warning').mockImplementation(() => {});

warning.resetCodeCache();
});
2 changes: 2 additions & 0 deletions jest.config.js
Expand Up @@ -3,4 +3,6 @@ module.exports = {
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/node_modules/**'],
coverageDirectory: 'coverage',
testURL: 'http://localhost/',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['./jest-setup.js'],
};
16 changes: 10 additions & 6 deletions package.json
Expand Up @@ -7,7 +7,7 @@
"types": "types/index.d.ts",
"scripts": {
"test": "NODE_ENV=test jest",
"test:watch": "jest --watch",
"test:watch": "NODE_ENV=test jest --watchAll",
"clean": "rimraf ./dist ./es ./tmp ./lib ./types",
"prepublishOnly": "npm run build",
"postpublish": "npm run clean",
Expand Down Expand Up @@ -53,23 +53,27 @@
},
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/core": "^7.14.6",
"@babel/plugin-external-helpers": "^7.10.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-react": "^7.10.4",
"@babel/preset-typescript": "^7.10.4",
"@types/jest": "26.0.9",
"@types/node": "^14.0.27",
"@testing-library/react": "11.2.7",
"@testing-library/jest-dom": "5.14.1",
"@types/jest": "^26.0.23",
"@types/node": "^15.12.2",
"@types/react": "^17.0.11",
"babel-eslint": "^10.1.0",
"babel-jest": "26.3.0",
"babel-jest": "27.0.2",
"babel-loader": "^8.0.6",
"coveralls": "3.1.0",
"cross-env": "^7.0.2",
"eslint": "^7.6.0",
"jest": "26.4.0",
"jest": "27.0.4",
"prettier": "^2.0.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"rimraf": "^3.0.2",
"typescript": "^4.3.2"
}
Expand Down
2 changes: 1 addition & 1 deletion src/focus-context.tsx
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
import createFocusStore from './focus-store';
import lrudInput from './lrud-input/focus-lrud';
import { ProviderValue, RootFocusNode, Orientation } from './types';
import warning from './utils/warning';
import { warning } from './utils/warning';

const FocusContext = React.createContext<null | ProviderValue>(null);

Expand Down
209 changes: 209 additions & 0 deletions src/focus-node.test.js
@@ -0,0 +1,209 @@
import React, { useRef } from 'react';
import '@testing-library/jest-dom';
import { render, act, fireEvent, screen } from '@testing-library/react';
import {
FocusRoot,
FocusNode,
useSetFocus,
useFocusStoreDangerously,
} from './index';
import { warning } from './utils/warning';

describe('FocusNode', () => {
it('warns when there is no FocusRoot', () => {
function TestComponent() {
return (
<>
<FocusNode focusId="nodeA" data-testid="nodeA" />
</>
);
}

expect(() => {
render(<TestComponent />);
}).toThrow();

expect(warning).toHaveBeenCalledTimes(2);
expect(warning.mock.calls[0][1]).toEqual('NO_FOCUS_PROVIDER_DETECTED');
// Note: I'm not entirely sure why the warning is called twice in this test...but that's OK
expect(warning.mock.calls[1][1]).toEqual('NO_FOCUS_PROVIDER_DETECTED');
});

describe('focusId', () => {
it('uses the ID that you provide', () => {
let focusStore;

function TestComponent() {
focusStore = useFocusStoreDangerously();

return (
<>
<FocusNode focusId="nodeA" />
<FocusNode focusId="nodeB" />
</>
);
}

render(
<FocusRoot>
<TestComponent />
</FocusRoot>
);

expect(focusStore.getState().focusedNodeId).toBe('nodeA');
});

it('generates its own ID when one is not provided', () => {
let focusStore;

function TestComponent() {
focusStore = useFocusStoreDangerously();

return (
<>
<FocusNode data-testid="nodeA" />
<FocusNode focusId="nodeB" data-testid="nodeB" />
</>
);
}

render(
<FocusRoot>
<TestComponent />
</FocusRoot>
);

expect(typeof focusStore.getState().focusedNodeId === 'string').toBe(
true
);
});

it('warns if an invalid ID is passed, but still generates a valid one', () => {
let focusStore;

function TestComponent() {
focusStore = useFocusStoreDangerously();

return (
<>
<FocusNode focusId={{ pasta: 'yum' }} />
<FocusNode focusId="nodeB" />
</>
);
}

render(
<FocusRoot>
<TestComponent />
</FocusRoot>
);

expect(typeof focusStore.getState().focusedNodeId === 'string').toBe(
true
);

expect(warning).toHaveBeenCalledTimes(1);
expect(warning.mock.calls[0][1]).toEqual('INVALID_FOCUS_ID_PASSED');
});

it('warns if the ID "root" is passed in, but still generates a valid one', () => {
let focusStore;

function TestComponent() {
focusStore = useFocusStoreDangerously();

return (
<>
<FocusNode focusId="root" />
<FocusNode focusId="nodeB" />
</>
);
}

render(
<FocusRoot>
<TestComponent />
</FocusRoot>
);

expect(typeof focusStore.getState().focusedNodeId === 'string').toBe(
true
);

expect(focusStore.getState().focusedNodeId).not.toBe('root');

expect(warning).toHaveBeenCalledTimes(1);
expect(warning.mock.calls[0][1]).toEqual('ROOT_ID_WAS_PASSED');
});
});

describe('propsFromNode', () => {
it('allows you to pass props based on the node', () => {
let focusStore;
let setFocus;

function TestComponent() {
focusStore = useFocusStoreDangerously();
setFocus = useSetFocus();

return (
<>
<FocusNode
focusId="nodeA"
data-testid="nodeA"
propsFromNode={(node) => {
return {
className: node.isFocused ? 'sandwiches' : 'spaghetti',
};
}}
/>
<FocusNode focusId="nodeB" />
</>
);
}

render(
<FocusRoot>
<TestComponent />
</FocusRoot>
);

expect(focusStore.getState().focusedNodeId).toBe('nodeA');
let nodeA = screen.getByTestId('nodeA');
expect(nodeA).toHaveClass('sandwiches');

act(() => setFocus('nodeB'));

expect(nodeA).toHaveClass('spaghetti');
});
});

describe('ref', () => {
it('allows you to pass a ref', () => {
let focusStore;
let elRef;

function TestComponent() {
focusStore = useFocusStoreDangerously();
elRef = useRef();

return (
<>
<FocusNode focusId="nodeA" data-testid="nodeA" ref={elRef} />
<FocusNode focusId="nodeB" />
</>
);
}

render(
<FocusRoot>
<TestComponent />
</FocusRoot>
);

expect(focusStore.getState().focusedNodeId).toBe('nodeA');
let nodeA = screen.getByTestId('nodeA');
expect(elRef.current).toBe(nodeA);
});
});
});
38 changes: 26 additions & 12 deletions src/focus-node.tsx
Expand Up @@ -10,7 +10,7 @@ import React, {
} from 'react';
import FocusContext from './focus-context';
import nodeFromDefinition from './utils/node-from-definition';
import warning from './utils/warning';
import { warning } from './utils/warning';
import {
FocusStore,
Id,
Expand Down Expand Up @@ -177,17 +177,30 @@ export function FocusNode(
);

const [nodeId] = useState(() => {
const isInvalidId = focusId === 'root';

if (isInvalidId) {
warning(
'A focus node with an invalid focus ID was created: "root". This is a reserved ID, so it has been ' +
'ignored. Please choose another ID if you wish to specify an ID.',
'ROOT_ID_WAS_PASSED'
);
const nonStringFocusId =
typeof focusId !== 'string' && focusId !== undefined;
const reservedFocusId = focusId === 'root';
const invalidNodeId = nonStringFocusId || reservedFocusId;

if (process.env.NODE_ENV !== 'production') {
if (reservedFocusId) {
warning(
'A focus node with an invalid focus ID was created: "root". This is a reserved ID, so it has been ' +
'ignored. Please choose another ID if you wish to specify an ID.',
'ROOT_ID_WAS_PASSED'
);
}

if (nonStringFocusId) {
warning(
'A focus node with an invalid focus ID was created: "root". This is a reserved ID, so it has been ' +
'ignored. Please choose another ID if you wish to specify an ID.',
'INVALID_FOCUS_ID_PASSED'
);
}
}

if (focusId && !isInvalidId) {
if (focusId && !invalidNodeId) {
return focusId;
} else {
const id = `node-${uniqueId}`;
Expand Down Expand Up @@ -459,15 +472,16 @@ export function FocusNode(
const isLeaf =
nodeRef.current && nodeRef.current.children.length === 0;
const isDisabled = nodeRef.current && nodeRef.current.disabled;

if (!isLeaf || isDisabled) {
return;
}

const focusState = staticDefinitions.providerValue.store.getState();

if (
!focusState._hasPointerEventsEnabled ||
!nodeExistsInTree.current
!nodeExistsInTree.current ||
focusState.interactionMode !== 'pointer'
) {
return;
}
Expand Down

0 comments on commit 598377c

Please sign in to comment.