Convert Seammammals plugin to Sandy {emoji:1f389}

Summary:
Converted the Seammammals plugin to use Sandy plugin infra (but the old components). Also updated lock file.

Added unittests have been added as well. The UI snapshots in there are kinda overkill, but nice demo that it works.

This completes the full roundtrip of the new Sandy infra, so this will be the last diff of this stack. I promise. I think.

Reviewed By: nikoant

Differential Revision: D22308265

fbshipit-source-id: 260e91a1951d486f6689880fe25281e80a71806a
This commit is contained in:
Michel Weststrate
2020-07-01 08:58:40 -07:00
committed by Facebook GitHub Bot
parent 581ddafd18
commit babc88e472
5 changed files with 243 additions and 122 deletions

View File

@@ -77,6 +77,5 @@ export function Component() {
// @ts-expect-error // @ts-expect-error
api.bla; api.bla;
// TODO N.b.: state updates won't be visible
return <h1>Hi from test plugin {count}</h1>; return <h1>Hi from test plugin {count}</h1>;
} }

View File

@@ -23,7 +23,8 @@
"prepack": "flipper-pkg lint && flipper-pkg bundle --production" "prepack": "flipper-pkg lint && flipper-pkg bundle --production"
}, },
"peerDependencies": { "peerDependencies": {
"flipper": "0.49.0" "flipper": "0.49.0",
"flipper-plugin": "0.49.0"
}, },
"devDependencies": { "devDependencies": {
"flipper": "0.49.0", "flipper": "0.49.0",

View File

@@ -0,0 +1,122 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import * as React from 'react';
import * as Flipper from 'flipper';
// eslint-disable-next-line
import {act} from '@testing-library/react';
{
// These mocks are needed because seammammals still uses Flipper in its UI implementation,
// so we need to mock some things
// @ts-ignore
jest.spyOn(Flipper.DetailSidebar, 'type').mockImplementation((props) => {
return <div className="DetailsSidebar">{props.children}</div>;
});
const origRequestIdleCallback = window.requestIdleCallback;
const origCancelIdleCallback = window.cancelIdleCallback;
// @ts-ignore
window.requestIdleCallback = (fn: () => void) => {
// the synchronous implementation forces DataInspector to render in sync
act(fn);
};
// @ts-ignore
window.cancelIdleCallback = clearImmediate;
afterAll(() => {
window.requestIdleCallback = origRequestIdleCallback;
window.cancelIdleCallback = origCancelIdleCallback;
});
}
import {TestUtils} from 'flipper-plugin';
import * as MammalsPlugin from '../';
test('It can store rows', () => {
const {instance, ...plugin} = TestUtils.startPlugin(MammalsPlugin);
expect(instance.rows.get()).toEqual({});
expect(instance.selectedID.get()).toBeNull();
plugin.sendEvent('newRow', {
id: 1,
title: 'Dolphin',
url: 'http://dolphin.png',
});
plugin.sendEvent('newRow', {
id: 2,
title: 'Turtle',
url: 'http://turtle.png',
});
expect(instance.rows.get()).toMatchInlineSnapshot(`
Object {
"1": Object {
"id": 1,
"title": "Dolphin",
"url": "http://dolphin.png",
},
"2": Object {
"id": 2,
"title": "Turtle",
"url": "http://turtle.png",
},
}
`);
});
test('It can have selection and render details', async () => {
const {instance, renderer, act, ...plugin} = TestUtils.renderPlugin(
MammalsPlugin,
);
expect(instance.rows.get()).toEqual({});
expect(instance.selectedID.get()).toBeNull();
plugin.sendEvent('newRow', {
id: 1,
title: 'Dolphin',
url: 'http://dolphin.png',
});
plugin.sendEvent('newRow', {
id: 2,
title: 'Turtle',
url: 'http://turtle.png',
});
// Dolphin card should now be visible
expect(await renderer.findByTestId('Dolphin')).not.toBeNull();
// Let's assert the structure of the Turtle card as well
expect(await renderer.findByTestId('Turtle')).toMatchInlineSnapshot(`
<div
class="css-ok7d66-View-FlexBox-FlexColumn"
data-testid="Turtle"
>
<div
class="css-vgz97s"
style="background-image: url(http://turtle.png);"
/>
<span
class="css-b2jb3d-Text"
>
Turtle
</span>
</div>
`);
// Nothing selected, so we should not have a sidebar
expect(renderer.queryAllByText('Extras').length).toBe(0);
act(() => {
instance.setSelection(2);
});
// Sidebar should be visible now
expect(await renderer.findByText('Extras')).not.toBeNull();
});

View File

@@ -11,14 +11,15 @@ import {
Text, Text,
Panel, Panel,
ManagedDataInspector, ManagedDataInspector,
FlipperPlugin,
DetailSidebar, DetailSidebar,
FlexRow, FlexRow,
FlexColumn, FlexColumn,
styled, styled,
colors, colors,
} from 'flipper'; } from 'flipper';
import React from 'react'; import React, {memo} from 'react';
import {FlipperClient, usePlugin, createState, useValue} from 'flipper-plugin';
type Id = number; type Id = number;
@@ -28,6 +29,10 @@ type Row = {
url: string; url: string;
}; };
type Events = {
newRow: Row;
};
function renderSidebar(row: Row) { function renderSidebar(row: Row) {
return ( return (
<Panel floating={false} heading={'Extras'}> <Panel floating={false} heading={'Extras'}>
@@ -44,105 +49,99 @@ type PersistedState = {
[key: string]: Row; [key: string]: Row;
}; };
export default class SeaMammals extends FlipperPlugin< export function plugin(client: FlipperClient<Events, {}>) {
State, const rows = createState<PersistedState>({});
any, const selectedID = createState<string | null>(null);
PersistedState
> {
static defaultPersistedState = {};
static persistedStateReducer<PersistedState>( client.onMessage('newRow', (row) => {
persistedState: PersistedState, rows.update((draft) => {
method: string, draft[row.id] = row;
payload: Row,
) {
if (method === 'newRow') {
return Object.assign({}, persistedState, {
[payload.id]: payload,
}); });
} });
return persistedState;
function setSelection(id: number) {
selectedID.set('' + id);
} }
static Container = styled(FlexRow)({ return {
rows,
selectedID,
setSelection,
};
}
export function Component() {
const instance = usePlugin(plugin);
const selectedID = useValue(instance.selectedID);
const rows = useValue(instance.rows);
return (
<Container>
{Object.entries(rows).map(([id, row]) => (
<Card
row={row}
onSelect={instance.setSelection}
selected={id === selectedID}
key={id}
/>
))}
<DetailSidebar>
{selectedID && renderSidebar(rows[selectedID])}
</DetailSidebar>
</Container>
);
}
type CardProps = {
onSelect: (id: number) => void;
selected: boolean;
row: Row;
};
const Card = memo(({row, selected, onSelect}: CardProps) => {
return (
<CardContainer
data-testid={row.title}
onClick={() => onSelect(row.id)}
selected={selected}>
<Image style={{backgroundImage: `url(${row.url || ''})`}} />
<Title>{row.title}</Title>
</CardContainer>
);
});
const Container = styled(FlexRow)({
backgroundColor: colors.macOSTitleBarBackgroundBlur, backgroundColor: colors.macOSTitleBarBackgroundBlur,
flexWrap: 'wrap', flexWrap: 'wrap',
alignItems: 'flex-start', alignItems: 'flex-start',
alignContent: 'flex-start', alignContent: 'flex-start',
flexGrow: 1, flexGrow: 1,
overflow: 'scroll', overflow: 'scroll',
}); });
state = { const CardContainer = styled(FlexColumn)<{selected?: boolean}>((props) => ({
selectedID: null as string | null,
};
render() {
const {selectedID} = this.state;
const {persistedState} = this.props;
return (
<SeaMammals.Container>
{Object.entries(persistedState).map(([id, row]) => (
<Card
{...row}
onSelect={() => this.setState({selectedID: id})}
selected={id === selectedID}
key={id}
/>
))}
<DetailSidebar>
{selectedID && renderSidebar(persistedState[selectedID])}
</DetailSidebar>
</SeaMammals.Container>
);
}
}
class Card extends React.Component<
{
onSelect: () => void;
selected: boolean;
} & Row
> {
static Container = styled(FlexColumn)<{selected?: boolean}>((props) => ({
margin: 10, margin: 10,
borderRadius: 5, borderRadius: 5,
border: '2px solid black', border: '2px solid black',
backgroundColor: colors.white, backgroundColor: colors.white,
borderColor: props.selected borderColor: props.selected ? colors.macOSTitleBarIconSelected : colors.white,
? colors.macOSTitleBarIconSelected
: colors.white,
padding: 0, padding: 0,
width: 150, width: 150,
overflow: 'hidden', overflow: 'hidden',
boxShadow: '1px 1px 4px rgba(0,0,0,0.1)', boxShadow: '1px 1px 4px rgba(0,0,0,0.1)',
cursor: 'pointer', cursor: 'pointer',
})); }));
static Image = styled.div({ const Image = styled.div({
backgroundSize: 'cover', backgroundSize: 'cover',
width: '100%', width: '100%',
paddingTop: '100%', paddingTop: '100%',
}); });
static Title = styled(Text)({ const Title = styled(Text)({
fontSize: 14, fontSize: 14,
fontWeight: 'bold', fontWeight: 'bold',
padding: '10px 5px', padding: '10px 5px',
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}); });
render() {
return (
<Card.Container
onClick={this.props.onSelect}
selected={this.props.selected}>
<Card.Image style={{backgroundImage: `url(${this.props.url || ''})`}} />
<Card.Title>{this.props.title}</Card.Title>
</Card.Container>
);
}
}

View File

@@ -5,7 +5,7 @@ PODS:
- ComponentKit (0.30): - ComponentKit (0.30):
- RenderCore (= 0.30) - RenderCore (= 0.30)
- Yoga (~> 1.14) - Yoga (~> 1.14)
- Flipper (0.48.0): - Flipper (0.49.0):
- Flipper-Folly (~> 2.2) - Flipper-Folly (~> 2.2)
- Flipper-RSocket (~> 1.1) - Flipper-RSocket (~> 1.1)
- Flipper-DoubleConversion (1.1.7) - Flipper-DoubleConversion (1.1.7)
@@ -19,41 +19,41 @@ PODS:
- Flipper-PeerTalk (0.0.4) - Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.1.0): - Flipper-RSocket (1.1.0):
- Flipper-Folly (~> 2.2) - Flipper-Folly (~> 2.2)
- FlipperKit (0.48.0): - FlipperKit (0.49.0):
- FlipperKit/Core (= 0.48.0) - FlipperKit/Core (= 0.49.0)
- FlipperKit/Core (0.48.0): - FlipperKit/Core (0.49.0):
- Flipper (~> 0.48.0) - Flipper (~> 0.49.0)
- FlipperKit/CppBridge - FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert - FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines - FlipperKit/FBDefines
- FlipperKit/FKPortForwarding - FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.48.0): - FlipperKit/CppBridge (0.49.0):
- Flipper (~> 0.48.0) - Flipper (~> 0.49.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.48.0): - FlipperKit/FBCxxFollyDynamicConvert (0.49.0):
- Flipper-Folly (~> 2.2) - Flipper-Folly (~> 2.2)
- FlipperKit/FBDefines (0.48.0) - FlipperKit/FBDefines (0.49.0)
- FlipperKit/FKPortForwarding (0.48.0): - FlipperKit/FKPortForwarding (0.49.0):
- CocoaAsyncSocket (~> 7.6) - CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4) - Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.48.0) - FlipperKit/FlipperKitHighlightOverlay (0.49.0)
- FlipperKit/FlipperKitLayoutComponentKitSupport (0.48.0): - FlipperKit/FlipperKitLayoutComponentKitSupport (0.49.0):
- ComponentKit (~> 0.30) - ComponentKit (~> 0.30)
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutPlugin - FlipperKit/FlipperKitLayoutPlugin
- FlipperKit/FlipperKitLayoutTextSearchable - FlipperKit/FlipperKitLayoutTextSearchable
- RenderCore (~> 0.30) - RenderCore (~> 0.30)
- FlipperKit/FlipperKitLayoutPlugin (0.48.0): - FlipperKit/FlipperKitLayoutPlugin (0.49.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable - FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18) - YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.48.0) - FlipperKit/FlipperKitLayoutTextSearchable (0.49.0)
- FlipperKit/FlipperKitNetworkPlugin (0.48.0): - FlipperKit/FlipperKitNetworkPlugin (0.49.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.48.0): - FlipperKit/FlipperKitUserDefaultsPlugin (0.49.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.48.0): - FlipperKit/SKIOSNetworkPlugin (0.49.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin - FlipperKit/FlipperKitNetworkPlugin
- OpenSSL-Universal (1.0.2.19): - OpenSSL-Universal (1.0.2.19):
@@ -65,10 +65,10 @@ PODS:
- Yoga (~> 1.14) - Yoga (~> 1.14)
DEPENDENCIES: DEPENDENCIES:
- FlipperKit (~> 0.48.0) - FlipperKit (~> 0.49.0)
- FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.48.0) - FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.49.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.48.0) - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.49.0)
- FlipperKit/SKIOSNetworkPlugin (~> 0.48.0) - FlipperKit/SKIOSNetworkPlugin (~> 0.49.0)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@@ -93,18 +93,18 @@ SPEC CHECKSUMS:
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
ComponentKit: c34da1ab3515cf18db68a4ba22c6599568d1de74 ComponentKit: c34da1ab3515cf18db68a4ba22c6599568d1de74
Flipper: ff9243fb80717f8310290396bd9f79be7c1a8016 Flipper: 78dc06980f2a5f7dbf28371a2800955430a95302
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7 Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
FlipperKit: a6208c63e203ceda87d99414755cc53615b749f3 FlipperKit: 26980c82fa2710f7ccc09eba394d1837258bd065
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
RenderCore: d779c47622b313ce2d51bb36d084517af38b0dc1 RenderCore: d779c47622b313ce2d51bb36d084517af38b0dc1
Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: db8648db517152c1225d37f97f0083975dfe8623 PODFILE CHECKSUM: eee0bd8fb136f35ceb29ff650a6d04bfb903dad1
COCOAPODS: 1.9.3 COCOAPODS: 1.9.3