From babc88e472612c66901d21d289bd217ef28ee385 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Wed, 1 Jul 2020 08:58:40 -0700 Subject: [PATCH] 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 --- .../src/__tests__/TestPlugin.tsx | 1 - desktop/plugins/seamammals/package.json | 3 +- .../src/__tests__/seammammals.node.tsx | 122 +++++++++++ desktop/plugins/seamammals/src/index.tsx | 191 +++++++++--------- iOS/Tutorial/Podfile.lock | 48 ++--- 5 files changed, 243 insertions(+), 122 deletions(-) create mode 100644 desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx diff --git a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx index 53cd5d7bf..41a601c43 100644 --- a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx +++ b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx @@ -77,6 +77,5 @@ export function Component() { // @ts-expect-error api.bla; - // TODO N.b.: state updates won't be visible return

Hi from test plugin {count}

; } diff --git a/desktop/plugins/seamammals/package.json b/desktop/plugins/seamammals/package.json index 4dca8562a..ed89335c7 100644 --- a/desktop/plugins/seamammals/package.json +++ b/desktop/plugins/seamammals/package.json @@ -23,7 +23,8 @@ "prepack": "flipper-pkg lint && flipper-pkg bundle --production" }, "peerDependencies": { - "flipper": "0.49.0" + "flipper": "0.49.0", + "flipper-plugin": "0.49.0" }, "devDependencies": { "flipper": "0.49.0", diff --git a/desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx b/desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx new file mode 100644 index 000000000..41a0394a8 --- /dev/null +++ b/desktop/plugins/seamammals/src/__tests__/seammammals.node.tsx @@ -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
{props.children}
; + }); + + 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(` +
+
+ + Turtle + +
+ `); + // 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(); +}); diff --git a/desktop/plugins/seamammals/src/index.tsx b/desktop/plugins/seamammals/src/index.tsx index 559a37a5e..174befdd4 100644 --- a/desktop/plugins/seamammals/src/index.tsx +++ b/desktop/plugins/seamammals/src/index.tsx @@ -11,14 +11,15 @@ import { Text, Panel, ManagedDataInspector, - FlipperPlugin, DetailSidebar, FlexRow, FlexColumn, styled, colors, } from 'flipper'; -import React from 'react'; +import React, {memo} from 'react'; + +import {FlipperClient, usePlugin, createState, useValue} from 'flipper-plugin'; type Id = number; @@ -28,6 +29,10 @@ type Row = { url: string; }; +type Events = { + newRow: Row; +}; + function renderSidebar(row: Row) { return ( @@ -44,105 +49,99 @@ type PersistedState = { [key: string]: Row; }; -export default class SeaMammals extends FlipperPlugin< - State, - any, - PersistedState -> { - static defaultPersistedState = {}; +export function plugin(client: FlipperClient) { + const rows = createState({}); + const selectedID = createState(null); - static persistedStateReducer( - persistedState: PersistedState, - method: string, - payload: Row, - ) { - if (method === 'newRow') { - return Object.assign({}, persistedState, { - [payload.id]: payload, - }); - } - return persistedState; - } - - static Container = styled(FlexRow)({ - backgroundColor: colors.macOSTitleBarBackgroundBlur, - flexWrap: 'wrap', - alignItems: 'flex-start', - alignContent: 'flex-start', - flexGrow: 1, - overflow: 'scroll', + client.onMessage('newRow', (row) => { + rows.update((draft) => { + draft[row.id] = row; + }); }); - state = { - selectedID: null as string | null, + function setSelection(id: number) { + selectedID.set('' + id); + } + + return { + rows, + selectedID, + setSelection, }; - - render() { - const {selectedID} = this.state; - const {persistedState} = this.props; - - return ( - - {Object.entries(persistedState).map(([id, row]) => ( - this.setState({selectedID: id})} - selected={id === selectedID} - key={id} - /> - ))} - - {selectedID && renderSidebar(persistedState[selectedID])} - - - ); - } } -class Card extends React.Component< - { - onSelect: () => void; - selected: boolean; - } & Row -> { - static Container = styled(FlexColumn)<{selected?: boolean}>((props) => ({ - margin: 10, - borderRadius: 5, - border: '2px solid black', - backgroundColor: colors.white, - borderColor: props.selected - ? colors.macOSTitleBarIconSelected - : colors.white, - padding: 0, - width: 150, - overflow: 'hidden', - boxShadow: '1px 1px 4px rgba(0,0,0,0.1)', - cursor: 'pointer', - })); +export function Component() { + const instance = usePlugin(plugin); + const selectedID = useValue(instance.selectedID); + const rows = useValue(instance.rows); - static Image = styled.div({ - backgroundSize: 'cover', - width: '100%', - paddingTop: '100%', - }); - - static Title = styled(Text)({ - fontSize: 14, - fontWeight: 'bold', - padding: '10px 5px', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }); - - render() { - return ( - - - {this.props.title} - - ); - } + return ( + + {Object.entries(rows).map(([id, row]) => ( + + ))} + + {selectedID && renderSidebar(rows[selectedID])} + + + ); } + +type CardProps = { + onSelect: (id: number) => void; + selected: boolean; + row: Row; +}; +const Card = memo(({row, selected, onSelect}: CardProps) => { + return ( + onSelect(row.id)} + selected={selected}> + + {row.title} + + ); +}); + +const Container = styled(FlexRow)({ + backgroundColor: colors.macOSTitleBarBackgroundBlur, + flexWrap: 'wrap', + alignItems: 'flex-start', + alignContent: 'flex-start', + flexGrow: 1, + overflow: 'scroll', +}); + +const CardContainer = styled(FlexColumn)<{selected?: boolean}>((props) => ({ + margin: 10, + borderRadius: 5, + border: '2px solid black', + backgroundColor: colors.white, + borderColor: props.selected ? colors.macOSTitleBarIconSelected : colors.white, + padding: 0, + width: 150, + overflow: 'hidden', + boxShadow: '1px 1px 4px rgba(0,0,0,0.1)', + cursor: 'pointer', +})); + +const Image = styled.div({ + backgroundSize: 'cover', + width: '100%', + paddingTop: '100%', +}); + +const Title = styled(Text)({ + fontSize: 14, + fontWeight: 'bold', + padding: '10px 5px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); diff --git a/iOS/Tutorial/Podfile.lock b/iOS/Tutorial/Podfile.lock index 94117f405..0a5571670 100644 --- a/iOS/Tutorial/Podfile.lock +++ b/iOS/Tutorial/Podfile.lock @@ -5,7 +5,7 @@ PODS: - ComponentKit (0.30): - RenderCore (= 0.30) - Yoga (~> 1.14) - - Flipper (0.48.0): + - Flipper (0.49.0): - Flipper-Folly (~> 2.2) - Flipper-RSocket (~> 1.1) - Flipper-DoubleConversion (1.1.7) @@ -19,41 +19,41 @@ PODS: - Flipper-PeerTalk (0.0.4) - Flipper-RSocket (1.1.0): - Flipper-Folly (~> 2.2) - - FlipperKit (0.48.0): - - FlipperKit/Core (= 0.48.0) - - FlipperKit/Core (0.48.0): - - Flipper (~> 0.48.0) + - FlipperKit (0.49.0): + - FlipperKit/Core (= 0.49.0) + - FlipperKit/Core (0.49.0): + - Flipper (~> 0.49.0) - FlipperKit/CppBridge - FlipperKit/FBCxxFollyDynamicConvert - FlipperKit/FBDefines - FlipperKit/FKPortForwarding - - FlipperKit/CppBridge (0.48.0): - - Flipper (~> 0.48.0) - - FlipperKit/FBCxxFollyDynamicConvert (0.48.0): + - FlipperKit/CppBridge (0.49.0): + - Flipper (~> 0.49.0) + - FlipperKit/FBCxxFollyDynamicConvert (0.49.0): - Flipper-Folly (~> 2.2) - - FlipperKit/FBDefines (0.48.0) - - FlipperKit/FKPortForwarding (0.48.0): + - FlipperKit/FBDefines (0.49.0) + - FlipperKit/FKPortForwarding (0.49.0): - CocoaAsyncSocket (~> 7.6) - Flipper-PeerTalk (~> 0.0.4) - - FlipperKit/FlipperKitHighlightOverlay (0.48.0) - - FlipperKit/FlipperKitLayoutComponentKitSupport (0.48.0): + - FlipperKit/FlipperKitHighlightOverlay (0.49.0) + - FlipperKit/FlipperKitLayoutComponentKitSupport (0.49.0): - ComponentKit (~> 0.30) - FlipperKit/Core - FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitLayoutPlugin - FlipperKit/FlipperKitLayoutTextSearchable - RenderCore (~> 0.30) - - FlipperKit/FlipperKitLayoutPlugin (0.48.0): + - FlipperKit/FlipperKitLayoutPlugin (0.49.0): - FlipperKit/Core - FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitLayoutTextSearchable - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutTextSearchable (0.48.0) - - FlipperKit/FlipperKitNetworkPlugin (0.48.0): + - FlipperKit/FlipperKitLayoutTextSearchable (0.49.0) + - FlipperKit/FlipperKitNetworkPlugin (0.49.0): - FlipperKit/Core - - FlipperKit/FlipperKitUserDefaultsPlugin (0.48.0): + - FlipperKit/FlipperKitUserDefaultsPlugin (0.49.0): - FlipperKit/Core - - FlipperKit/SKIOSNetworkPlugin (0.48.0): + - FlipperKit/SKIOSNetworkPlugin (0.49.0): - FlipperKit/Core - FlipperKit/FlipperKitNetworkPlugin - OpenSSL-Universal (1.0.2.19): @@ -65,10 +65,10 @@ PODS: - Yoga (~> 1.14) DEPENDENCIES: - - FlipperKit (~> 0.48.0) - - FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.48.0) - - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.48.0) - - FlipperKit/SKIOSNetworkPlugin (~> 0.48.0) + - FlipperKit (~> 0.49.0) + - FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.49.0) + - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.49.0) + - FlipperKit/SKIOSNetworkPlugin (~> 0.49.0) SPEC REPOS: trunk: @@ -93,18 +93,18 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f ComponentKit: c34da1ab3515cf18db68a4ba22c6599568d1de74 - Flipper: ff9243fb80717f8310290396bd9f79be7c1a8016 + Flipper: 78dc06980f2a5f7dbf28371a2800955430a95302 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7 - FlipperKit: a6208c63e203ceda87d99414755cc53615b749f3 + FlipperKit: 26980c82fa2710f7ccc09eba394d1837258bd065 OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 RenderCore: d779c47622b313ce2d51bb36d084517af38b0dc1 Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: db8648db517152c1225d37f97f0083975dfe8623 +PODFILE CHECKSUM: eee0bd8fb136f35ceb29ff650a6d04bfb903dad1 COCOAPODS: 1.9.3