Refactor PluginInstaller component
Summary: - Made side-effecting elements injectable via props. - Added default props so usage doesn't change. - Added testing-library/react snapshot test that waits for test data to appear in the list. One slightly annoying part here is that this now that we have an `autoHeight` prop which is only useful for testing it as it prevents a problem with a height-detection in the test runner. We could even change the default as it doesn't affect the display in prod, but this still feels slightly cleaner. Reviewed By: jknoxville Differential Revision: D17808510 fbshipit-source-id: 2ae70886c58282d5bdc98ba4215e8248e4c7f159
This commit is contained in:
committed by
Facebook Github Bot
parent
3730c523ec
commit
04e12a28a0
@@ -41,7 +41,7 @@ const PluginManager = new PM({
|
|||||||
ignoredDependencies: ['flipper', 'react', 'react-dom', '@types/*'],
|
ignoredDependencies: ['flipper', 'react', 'react-dom', '@types/*'],
|
||||||
});
|
});
|
||||||
|
|
||||||
type PluginDefinition = {
|
export type PluginDefinition = {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -92,10 +92,31 @@ const RestartBar = styled(FlexColumn)({
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function() {
|
type Props = {
|
||||||
|
searchIndexFactory: () => algoliasearch.Index;
|
||||||
|
getInstalledPlugins: () => Promise<Map<string, PluginDefinition>>;
|
||||||
|
autoHeight: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps: Props = {
|
||||||
|
searchIndexFactory: () => {
|
||||||
|
const client = algoliasearch(ALGOLIA_APPLICATION_ID, ALGOLIA_API_KEY);
|
||||||
|
return client.initIndex('npm-search');
|
||||||
|
},
|
||||||
|
getInstalledPlugins: _getInstalledPlugins,
|
||||||
|
autoHeight: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PluginInstaller = function props(props: Props) {
|
||||||
const [restartRequired, setRestartRequired] = useState(false);
|
const [restartRequired, setRestartRequired] = useState(false);
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const rows = useNPMSearch(setRestartRequired, query, setQuery);
|
const rows = useNPMSearch(
|
||||||
|
setRestartRequired,
|
||||||
|
query,
|
||||||
|
setQuery,
|
||||||
|
props.searchIndexFactory,
|
||||||
|
props.getInstalledPlugins,
|
||||||
|
);
|
||||||
const restartApp = useCallback(() => {
|
const restartApp = useCallback(() => {
|
||||||
remote.app.relaunch();
|
remote.app.relaunch();
|
||||||
remote.app.exit();
|
remote.app.exit();
|
||||||
@@ -126,11 +147,14 @@ export default function() {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
highlightableRows={false}
|
highlightableRows={false}
|
||||||
highlightedRows={new Set()}
|
highlightedRows={new Set()}
|
||||||
|
autoHeight={props.autoHeight}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
PluginInstaller.defaultProps = defaultProps;
|
||||||
|
export default PluginInstaller;
|
||||||
|
|
||||||
const TableButton = styled(Button)({
|
const TableButton = styled(Button)({
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
@@ -248,29 +272,27 @@ function useNPMSearch(
|
|||||||
setRestartRequired: (restart: boolean) => void,
|
setRestartRequired: (restart: boolean) => void,
|
||||||
query: string,
|
query: string,
|
||||||
setQuery: (query: string) => void,
|
setQuery: (query: string) => void,
|
||||||
|
searchClientFactory: () => algoliasearch.Index,
|
||||||
|
getInstalledPlugins: () => Promise<Map<string, PluginDefinition>>,
|
||||||
): TableRows_immutable {
|
): TableRows_immutable {
|
||||||
const index = useMemo(() => {
|
const index = useMemo(searchClientFactory, []);
|
||||||
const client = algoliasearch(ALGOLIA_APPLICATION_ID, ALGOLIA_API_KEY);
|
|
||||||
return client.initIndex('npm-search');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [installedPlugins, setInstalledPlugins] = useState(
|
const [installedPlugins, setInstalledPlugins] = useState(
|
||||||
new Map<string, PluginDefinition>(),
|
new Map<string, PluginDefinition>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const getAndSetInstalledPlugins = () =>
|
||||||
reportUsage(`${TAG}:open`);
|
|
||||||
reportPlatformFailures(
|
reportPlatformFailures(
|
||||||
getInstalledPlugins(),
|
getInstalledPlugins(),
|
||||||
`${TAG}:getInstalledPlugins`,
|
`${TAG}:getInstalledPlugins`,
|
||||||
).then(setInstalledPlugins);
|
).then(setInstalledPlugins);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reportUsage(`${TAG}:open`);
|
||||||
|
getAndSetInstalledPlugins();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onInstall = useCallback(async () => {
|
const onInstall = useCallback(async () => {
|
||||||
reportPlatformFailures(
|
getAndSetInstalledPlugins();
|
||||||
getInstalledPlugins(),
|
|
||||||
`${TAG}:getInstalledPlugins`,
|
|
||||||
).then(setInstalledPlugins);
|
|
||||||
setRestartRequired(true);
|
setRestartRequired(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -336,7 +358,7 @@ function useNPMSearch(
|
|||||||
return List(results.map(createRow));
|
return List(results.map(createRow));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getInstalledPlugins() {
|
async function _getInstalledPlugins(): Promise<Map<string, PluginDefinition>> {
|
||||||
const dirs = await fs.readdir(PLUGIN_DIR);
|
const dirs = await fs.readdir(PLUGIN_DIR);
|
||||||
const plugins = await Promise.all<[string, PluginDefinition]>(
|
const plugins = await Promise.all<[string, PluginDefinition]>(
|
||||||
dirs.map(
|
dirs.map(
|
||||||
|
|||||||
52
src/chrome/__tests__/PluginInstaller.node.tsx
Normal file
52
src/chrome/__tests__/PluginInstaller.node.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {default as PluginInstaller, PluginDefinition} from '../PluginInstaller';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {render, waitForElement} from '@testing-library/react';
|
||||||
|
import {init as initLogger} from '../../fb-stubs/Logger';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
const mockStore = configureStore([])({application: {sessionId: 'mysession'}});
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
|
|
||||||
|
const SEARCH_RESULTS = ({
|
||||||
|
hits: [
|
||||||
|
{name: 'flipper-plugin-hello', version: '0.1.0', description: 'World?'},
|
||||||
|
{name: 'flipper-plugin-world', version: '0.2.0', description: 'Hello?'},
|
||||||
|
],
|
||||||
|
} as unknown) as algoliasearch.Response<any>;
|
||||||
|
|
||||||
|
// *Very* incomplete mock, but that's all we use.
|
||||||
|
const indexMock: algoliasearch.Index = ({
|
||||||
|
search: jest.fn(),
|
||||||
|
} as unknown) as algoliasearch.Index;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
indexMock.search = jest.fn(async () => SEARCH_RESULTS);
|
||||||
|
initLogger(mockStore as any, {isTest: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('load PluginInstaller list', async () => {
|
||||||
|
const component = (
|
||||||
|
<Provider store={mockStore}>
|
||||||
|
<PluginInstaller
|
||||||
|
getInstalledPlugins={async () => new Map<string, PluginDefinition>()}
|
||||||
|
searchIndexFactory={() => indexMock}
|
||||||
|
// Bit ugly to have this as an effectively test-only option, but
|
||||||
|
// without, we rely on height information from Electron which we don't
|
||||||
|
// have, causing no items to be rendered.
|
||||||
|
autoHeight={true}
|
||||||
|
/>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
const {container, getByText} = render(component);
|
||||||
|
await waitForElement(() => getByText('flipper-plugin-hello'));
|
||||||
|
expect((indexMock.search as jest.Mock).mock.calls.length).toBe(2);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
238
src/chrome/__tests__/__snapshots__/PluginInstaller.node.tsx.snap
Normal file
238
src/chrome/__tests__/__snapshots__/PluginInstaller.node.tsx.snap
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`load PluginInstaller list 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="css-1nm3y77"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-4zga7z"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-e0frhe"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="css-ww9vf1"
|
||||||
|
placeholder="Search Flipper plugins..."
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-1nekdsz"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1k4677w"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-vd30c4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1vb6sy6"
|
||||||
|
title="name"
|
||||||
|
width="25%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-glr1wj"
|
||||||
|
style="z-index: auto; right: 0px; bottom: 0px; width: 100%; height: 100%;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1db3q1"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-1xchw24"
|
||||||
|
title="version"
|
||||||
|
width="10%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-glr1wj"
|
||||||
|
style="z-index: auto; right: 0px; bottom: 0px; width: 100%; height: 100%;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1db3q1"
|
||||||
|
>
|
||||||
|
Version
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-173sh01"
|
||||||
|
title="description"
|
||||||
|
width="flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-glr1wj"
|
||||||
|
style="z-index: auto; right: 0px; bottom: 0px; width: 100%; height: 100%;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1db3q1"
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-f0p3z5"
|
||||||
|
title="install"
|
||||||
|
width="15%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-glr1wj"
|
||||||
|
style="z-index: auto; right: 0px; bottom: 0px; width: 100%; height: 100%;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1db3q1"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-1nekdsz"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1jjayz9"
|
||||||
|
data-key="flipper-plugin-hello"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1jes4os"
|
||||||
|
title=""
|
||||||
|
width="25%"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1pso8q0"
|
||||||
|
>
|
||||||
|
flipper-plugin-hello
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-1kkefm9"
|
||||||
|
title=""
|
||||||
|
width="10%"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1pso8q0"
|
||||||
|
>
|
||||||
|
0.1.0
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-v10b1x"
|
||||||
|
title=""
|
||||||
|
width="flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-9qtipk"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1pso8q0"
|
||||||
|
>
|
||||||
|
World?
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="css-12zzrdt"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="css-1b8mb9l"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-17bs008"
|
||||||
|
color="#bec2c9"
|
||||||
|
size="16"
|
||||||
|
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-yt1ntn"
|
||||||
|
title=""
|
||||||
|
width="15%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-z1ci4f"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-1ut44ht"
|
||||||
|
data-key="flipper-plugin-world"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-1jes4os"
|
||||||
|
title=""
|
||||||
|
width="25%"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1pso8q0"
|
||||||
|
>
|
||||||
|
flipper-plugin-world
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-1kkefm9"
|
||||||
|
title=""
|
||||||
|
width="10%"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1pso8q0"
|
||||||
|
>
|
||||||
|
0.2.0
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-v10b1x"
|
||||||
|
title=""
|
||||||
|
width="flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-9qtipk"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="css-1pso8q0"
|
||||||
|
>
|
||||||
|
Hello?
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="css-12zzrdt"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="css-1b8mb9l"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-17bs008"
|
||||||
|
color="#bec2c9"
|
||||||
|
size="16"
|
||||||
|
src="https://external.xx.fbcdn.net/assets/?name=info-circle&variant=filled&size=16&set=facebook_icons&density=1x"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-yt1ntn"
|
||||||
|
title=""
|
||||||
|
width="15%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-z1ci4f"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user