@desklink/react-native 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,205 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import React from 'react';
3
- import TestRenderer, { act } from 'react-test-renderer';
4
-
5
- (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
6
-
7
- /** The key row and the clipboard pill, rendered with `react-native` primitives as host strings. */
8
- vi.mock('react-native', () => ({
9
- StyleSheet: { create: (styles: unknown) => styles, absoluteFill: {} },
10
- Linking: { getInitialURL: async () => null, addEventListener: () => ({ remove: () => undefined }) },
11
- Platform: { OS: 'ios' },
12
- Pressable: 'Pressable',
13
- ScrollView: 'ScrollView',
14
- StatusBar: 'StatusBar',
15
- Text: 'Text',
16
- View: 'View',
17
- Settings: { get: () => 'ws://localhost/desktop?token=test' },
18
- }));
19
-
20
- vi.mock('react-native-safe-area-context', () => ({
21
- SafeAreaProvider: ({ children }: { children: React.ReactNode }) => children,
22
- useSafeAreaInsets: () => ({ top: 0, left: 0, bottom: 0, right: 0 }),
23
- }));
24
-
25
- const example = vi.hoisted(() => ({
26
- write: vi.fn(),
27
- copy: vi.fn(),
28
- connect: vi.fn(),
29
- }));
30
- vi.mock('expo-clipboard', () => ({ setStringAsync: example.write }));
31
- vi.mock('@desklink/react-native', async () => ({
32
- CONTROL_PERMISSIONS: ['view', 'control', 'clipboard'],
33
- ClipboardConfirmation: (await import('./ClipboardConfirmation')).ClipboardConfirmation,
34
- ModifierKeys: (await import('./ModifierKeys')).ModifierKeys,
35
- DesktopView: 'DesktopView',
36
- useDesktopSession: () => ({
37
- snapshot: { status: 'live' },
38
- nativeId: 'example-session',
39
- modifiers: { Control: 'off', Shift: 'off', Alt: 'off', Meta: 'off' },
40
- clipboard: null,
41
- connect: example.connect,
42
- setInputEnabled: vi.fn(),
43
- copyRemoteToLocal: example.copy,
44
- tapModifier: vi.fn(),
45
- }),
46
- }));
47
-
48
- import { KEY_COLORS, ModifierKeys } from './ModifierKeys';
49
- import { CLIPBOARD_CONFIRMATION_MS, ClipboardConfirmation, PILL_COLORS, describeTransfer } from './ClipboardConfirmation';
50
- import { NO_MODIFIERS } from './stickyModifiers';
51
- import type { ClipboardTransfer } from './useDesktopSession';
52
- import App from '../example/App';
53
-
54
- type Style = Record<string, unknown>;
55
- const flatten = (style: unknown): Style =>
56
- Array.isArray(style) ? Object.assign({}, ...style.map(flatten)) : style ? (style as Style) : {};
57
-
58
- /** WCAG contrast of two colours, the first drawn with its alpha over `under`. */
59
- function contrast(top: string, bottom: string, under = '#000000'): number {
60
- const rgba = (c: string) => {
61
- const m = c.match(/rgba?\(([^)]+)\)/);
62
- if (m) { const [r, g, b, a = 1] = m[1].split(',').map(Number); return [r, g, b, a]; }
63
- return [1, 3, 5].map((i) => parseInt(c.slice(i, i + 2), 16)).concat(1);
64
- };
65
- const over = (c: string) => {
66
- const [r, g, b, a] = rgba(c);
67
- const [ur, ug, ub] = rgba(under);
68
- return [r * a + ur * (1 - a), g * a + ug * (1 - a), b * a + ub * (1 - a)];
69
- };
70
- const lum = (rgb: number[]) => {
71
- const [r, g, b] = rgb.map((v) => { const s = v / 255; return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; });
72
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
73
- };
74
- const [l1, l2] = [lum(over(top)), lum(over(bottom))].sort((a, b) => b - a);
75
- return (l1 + 0.05) / (l2 + 0.05);
76
- }
77
-
78
- describe('ModifierKeys', () => {
79
- function render(modifiers = NO_MODIFIERS, onTap = vi.fn()) {
80
- let tree!: TestRenderer.ReactTestRenderer;
81
- act(() => { tree = TestRenderer.create(<ModifierKeys modifiers={modifiers} onTap={onTap} />); });
82
- const key = (name: string) => tree.root.find((node) => node.props.testID === `desklink-key-${name}`);
83
- return { key, onTap };
84
- }
85
-
86
- it('draws off, latched and locked as three different keys and names each state', () => {
87
- const { key } = render({ Control: 'off', Shift: 'once', Alt: 'lock', Meta: 'off' });
88
- const look = (name: string) => {
89
- const node = key(name);
90
- const [, bar] = node.props.children;
91
- return { key: flatten(node.props.style), bar: flatten(bar.props.style), said: node.props.accessibilityValue.text };
92
- };
93
- const off = look('Control');
94
- const latched = look('Shift');
95
- const locked = look('Alt');
96
- expect([off.said, latched.said, locked.said]).toEqual(['off', 'latched', 'locked']);
97
- expect(off.key.backgroundColor).not.toBe(latched.key.backgroundColor);
98
- // Latched and locked share a fill and differ in shape: the rim and the bar.
99
- expect(locked.key.borderColor).not.toBe(latched.key.borderColor);
100
- expect(locked.bar.backgroundColor).not.toBe(latched.bar.backgroundColor);
101
- expect(key('Control').props.accessibilityState).toEqual({ selected: false });
102
- expect(key('Shift').props.accessibilityState).toEqual({ selected: true });
103
- });
104
-
105
- it('hands a tap to the session', () => {
106
- const { key, onTap } = render();
107
- key('Control').props.onPress();
108
- expect(onTap).toHaveBeenCalledWith('Control');
109
- });
110
-
111
- it('keeps every label at 4.5:1 or better', () => {
112
- expect(contrast(KEY_COLORS.offLabel, KEY_COLORS.off)).toBeGreaterThanOrEqual(4.5);
113
- expect(contrast(KEY_COLORS.onLabel, KEY_COLORS.on)).toBeGreaterThanOrEqual(4.5);
114
- });
115
- });
116
-
117
- describe('ClipboardConfirmation', () => {
118
- beforeEach(() => { vi.useFakeTimers(); });
119
- afterEach(() => { vi.useRealTimers(); });
120
-
121
- const transfer = (id: number, text = 'hello world'): ClipboardTransfer => ({ id, direction: 'to-desktop', text, truncated: false });
122
-
123
- it('shows each transfer, ignores touches, and goes away after about 1.5 s', () => {
124
- let tree!: TestRenderer.ReactTestRenderer;
125
- act(() => { tree = TestRenderer.create(<ClipboardConfirmation transfer={null} />); });
126
- expect(tree.toJSON()).toBeNull();
127
-
128
- act(() => { tree.update(<ClipboardConfirmation transfer={transfer(1)} />); });
129
- const place = tree.toJSON() as TestRenderer.ReactTestRendererJSON;
130
- expect(place.props.pointerEvents).toBe('none');
131
- expect(flatten(place.props.style).position).toBeUndefined();
132
- expect(JSON.stringify(place)).toContain('Sent to desktop · “hello world”');
133
-
134
- act(() => { vi.advanceTimersByTime(CLIPBOARD_CONFIRMATION_MS - 1); });
135
- expect(tree.toJSON()).not.toBeNull();
136
- act(() => { vi.advanceTimersByTime(1); });
137
- expect(tree.toJSON()).toBeNull();
138
-
139
- // The same text again is a new transfer and is confirmed again.
140
- act(() => { tree.update(<ClipboardConfirmation transfer={transfer(2)} />); });
141
- expect(tree.toJSON()).not.toBeNull();
142
- });
143
-
144
- it('describes where the text went and how it starts, with no byte count', () => {
145
- expect(describeTransfer({ id: 1, direction: 'to-phone', text: 'a\n b', truncated: false })).toBe('Copied to phone · “a b”');
146
- expect(describeTransfer({ id: 1, direction: 'to-phone', text: 'x'.repeat(2048), truncated: true })).toBe(`Copied to phone · “${'x'.repeat(24)}…”`);
147
- expect(describeTransfer({ id: 1, direction: 'to-desktop', text: ' ', truncated: false })).toBe('Sent to desktop');
148
- });
149
-
150
- it('reads at 4.5:1 or better over a white or a black desktop', () => {
151
- expect(contrast(PILL_COLORS.text, PILL_COLORS.backing, '#ffffff')).toBeGreaterThanOrEqual(4.5);
152
- expect(contrast(PILL_COLORS.text, PILL_COLORS.backing, '#000000')).toBeGreaterThanOrEqual(4.5);
153
- });
154
- });
155
-
156
- describe('example Copy', () => {
157
- it('writes only on a tap and retains a backed accessible failure until the next tap', async () => {
158
- example.write.mockReset();
159
- example.copy.mockReset();
160
- example.copy.mockImplementation(async (write: (text: string) => Promise<void>) => { await write('desktop text'); });
161
- let tree!: TestRenderer.ReactTestRenderer;
162
- await act(async () => { tree = TestRenderer.create(<App />); });
163
- expect(example.write).not.toHaveBeenCalled();
164
- const tap = () => tree.root.findByProps({ testID: 'desklink-copy' }).props.onPress();
165
-
166
- example.copy.mockRejectedValueOnce(new Error('clipboard is unavailable for this desktop source'));
167
- await act(async () => { tap(); });
168
- expect(tree.root.findByProps({ testID: 'desklink-copy-error' }).findByType('Text').props.children)
169
- .toBe('Clipboard failed: clipboard is unavailable for this desktop source');
170
- expect(example.write).not.toHaveBeenCalled();
171
-
172
- example.write.mockResolvedValue(false);
173
- await act(async () => { tap(); });
174
- expect(example.write).toHaveBeenCalledWith('desktop text');
175
- const error = tree.root.findByProps({ testID: 'desklink-copy-error' });
176
- expect(error.props.accessible).toBe(true);
177
- expect(error.props.accessibilityRole).toBe('alert');
178
- expect(error.props.accessibilityLiveRegion).toBe('polite');
179
- const style = flatten(error.props.style);
180
- expect(style.paddingHorizontal).toBeGreaterThan(0);
181
- expect(style.paddingVertical).toBeGreaterThan(0);
182
- expect(contrast('#ffffff', style.backgroundColor as string)).toBeGreaterThanOrEqual(4.5);
183
- expect(error.findByType('Text').props.children).toBe('Clipboard failed: The phone refused the clipboard write.');
184
- expect(tree.root.findAllByProps({ testID: 'desklink-clipboard' })).toHaveLength(0);
185
-
186
- vi.useFakeTimers();
187
- try {
188
- act(() => { vi.advanceTimersByTime(5000); });
189
- expect(tree.root.findAllByProps({ testID: 'desklink-copy-error' })).toHaveLength(1);
190
- } finally { vi.useRealTimers(); }
191
-
192
- example.write.mockRejectedValue(new Error('native write refused'));
193
- await act(async () => { tap(); });
194
- expect(tree.root.findByProps({ testID: 'desklink-copy-error' }).findByType('Text').props.children).toBe('Clipboard failed: native write refused');
195
-
196
- let finish!: (written: boolean) => void;
197
- example.write.mockImplementation(() => new Promise<boolean>((resolve) => { finish = resolve; }));
198
- await act(async () => { tap(); });
199
- expect(tree.root.findAllByProps({ testID: 'desklink-copy-error' })).toHaveLength(0);
200
- expect(tree.root.findAllByProps({ testID: 'desklink-clipboard' })).toHaveLength(0);
201
- await act(async () => { finish(true); });
202
- expect(tree.root.findAllByProps({ testID: 'desklink-copy-error' })).toHaveLength(0);
203
- act(() => { tree.unmount(); });
204
- });
205
- });