Skip to content

Commit cb9dbe9

Browse files
committed
開発者オーバレイのUIと長押しメニュー切り替えを整備
1 parent c9c326e commit cb9dbe9

File tree

6 files changed

+806
-190
lines changed

6 files changed

+806
-190
lines changed

assets/translations/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@
205205
"canaryNotice": "This TrainLCD is a canary release.",
206206
"setAsTerminus": "Set as terminus",
207207
"optInTelemetryTitle": "Enable telemetry",
208+
"showDevOverlay": "Show developer overlay",
209+
"hideDevOverlay": "Hide developer overlay",
208210
"telemetrySettingWillPersist": "Enabling telemetry will be persisted.",
209211
"personalize": "Personalize",
210212
"forDevelopers": "Developers",

assets/translations/ja.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@
206206
"canaryNotice": "このTrainLCDはカナリアリリースです。",
207207
"setAsTerminus": "終着駅に設定する",
208208
"optInTelemetryTitle": "テレメトリを有効にする",
209+
"showDevOverlay": "開発者オーバレイを表示",
210+
"hideDevOverlay": "開発者オーバレイを非表示",
209211
"telemetrySettingWillPersist": "テレメトリ有効化は永続的に保持されます。",
210212
"personalize": "パーソナライズ",
211213
"forDevelopers": "開発者向け",

src/components/DevOverlay.test.tsx

Lines changed: 135 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { render } from '@testing-library/react-native';
22
import * as Application from 'expo-application';
33
import { useAtomValue } from 'jotai';
44
import type { Station } from '~/@types/graphql';
5+
import {
6+
accuracyHistoryAtom,
7+
backgroundLocationTrackingAtom,
8+
locationAtom,
9+
} from '~/store/atoms/location';
510
import DevOverlay from './DevOverlay';
611

712
jest.mock('jotai', () => {
@@ -62,20 +67,36 @@ const mockUseNextStation = useNextStation as jest.MockedFunction<
6267
>;
6368

6469
describe('DevOverlay', () => {
65-
beforeEach(() => {
66-
// Default mock implementations for useAtomValue
67-
// locationAtom, accuracyHistoryAtom, backgroundLocationTrackingAtomの順で呼ばれる
68-
// (useTelemetryEnabledは別途モック済み)
69-
mockUseAtomValue
70-
.mockReturnValueOnce({
71-
coords: {
72-
speed: 10,
73-
accuracy: 15,
74-
},
75-
}) // locationAtom
76-
.mockReturnValueOnce([10, 15, 20]) // accuracyHistoryAtom
77-
.mockReturnValue(false); // backgroundLocationTrackingAtom
70+
const setupAtomValues = ({
71+
location = {
72+
coords: {
73+
speed: 10,
74+
accuracy: 15,
75+
},
76+
},
77+
accuracyHistory = [10, 15, 20],
78+
backgroundLocationTracking = false,
79+
}: {
80+
location?: unknown;
81+
accuracyHistory?: unknown;
82+
backgroundLocationTracking?: boolean;
83+
} = {}) => {
84+
mockUseAtomValue.mockImplementation((atom) => {
85+
if (atom === locationAtom) {
86+
return location as never;
87+
}
88+
if (atom === accuracyHistoryAtom) {
89+
return accuracyHistory as never;
90+
}
91+
if (atom === backgroundLocationTrackingAtom) {
92+
return backgroundLocationTracking as never;
93+
}
94+
return undefined as never;
95+
});
96+
};
7897

98+
beforeEach(() => {
99+
setupAtomValues();
79100
mockUseDistanceToNextStation.mockReturnValue('500');
80101
mockUseNextStation.mockReturnValue({
81102
id: 1,
@@ -106,125 +127,139 @@ describe('DevOverlay', () => {
106127

107128
it('テレメトリー状態を表示する', () => {
108129
const { getByText } = render(<DevOverlay />);
109-
expect(getByText('Telemetry: ON')).toBeTruthy();
130+
expect(getByText('TELEMETRY')).toBeTruthy();
131+
expect(getByText('ON')).toBeTruthy();
110132
});
111133

112134
it('バックグラウンド位置情報のOFF状態を表示する', () => {
113135
const { getByText } = render(<DevOverlay />);
114-
expect(getByText('BG Loc: OFF')).toBeTruthy();
136+
expect(getByText('BG LOC')).toBeTruthy();
137+
expect(getByText('OFF')).toBeTruthy();
115138
});
116139

117140
it('バックグラウンド位置情報のON状態を表示する', () => {
118-
mockUseAtomValue.mockReset();
119-
mockUseAtomValue
120-
.mockReturnValueOnce({
141+
setupAtomValues({
142+
location: {
121143
coords: { speed: 10, accuracy: 15 },
122-
}) // locationAtom
123-
.mockReturnValueOnce([10, 15, 20]) // accuracyHistoryAtom
124-
.mockReturnValue(true); // backgroundLocationTrackingAtom
144+
},
145+
accuracyHistory: [10, 15, 20],
146+
backgroundLocationTracking: true,
147+
});
125148

126-
const { getByText } = render(<DevOverlay />);
127-
expect(getByText('BG Loc: ON')).toBeTruthy();
149+
const { getByText, getAllByText } = render(<DevOverlay />);
150+
expect(getByText('BG LOC')).toBeTruthy();
151+
expect(getAllByText('ON')).toHaveLength(2);
128152
});
129153
});
130154

131155
describe('位置情報の表示', () => {
132156
it('精度情報を表示する', () => {
133-
const { getByText } = render(<DevOverlay />);
134-
expect(getByText('Accuracy: 15m')).toBeTruthy();
157+
const { getByText, getByTestId } = render(<DevOverlay />);
158+
expect(getByText('LOCATION ACCURACY')).toBeTruthy();
159+
expect(getByTestId('dev-overlay-accuracy-value')).toHaveTextContent(
160+
'15m'
161+
);
135162
});
136163

137164
it('速度情報をkm/hで表示する', () => {
138-
const { getByText } = render(<DevOverlay />);
139-
// speed: 10 m/s = 36 km/h
140-
expect(getByText('Speed: 36km/h')).toBeTruthy();
165+
const { getByText, getByTestId } = render(<DevOverlay />);
166+
expect(getByText('CURRENT SPEED')).toBeTruthy();
167+
expect(getByTestId('dev-overlay-speed-value')).toHaveTextContent(
168+
'36km/h'
169+
);
141170
});
142171

143172
it('次の駅までの距離を表示する', () => {
144-
const { getByText } = render(<DevOverlay />);
145-
expect(getByText(/Next: 500m /)).toBeTruthy();
173+
const { getByText, getByTestId } = render(<DevOverlay />);
174+
expect(getByText('NEXT TARGET')).toBeTruthy();
175+
expect(getByTestId('dev-overlay-next-value')).toHaveTextContent('500m');
176+
expect(getByTestId('dev-overlay-next-meta')).toHaveTextContent(
177+
'テスト駅'
178+
);
146179
});
147180

148181
it('精度チャートを表示する', () => {
149-
const { getByText } = render(<DevOverlay />);
150-
// accuracyHistory: [10, 15, 20] -> '▇▇▇'
151-
expect(getByText('▇▇▇')).toBeTruthy();
182+
const { getByTestId } = render(<DevOverlay />);
183+
expect(getByTestId('dev-overlay-accuracy-history')).toHaveTextContent(
184+
'▇▇▇'
185+
);
152186
});
153187
});
154188

155189
describe('エッジケース', () => {
156190
it('位置情報がnullの場合にクラッシュしない', () => {
157-
mockUseAtomValue.mockReset();
158-
mockUseAtomValue
159-
.mockReturnValueOnce(null) // locationAtom
160-
.mockReturnValueOnce([]) // accuracyHistoryAtom
161-
.mockReturnValue(false); // backgroundLocationTrackingAtom
191+
setupAtomValues({
192+
location: null,
193+
accuracyHistory: [],
194+
backgroundLocationTracking: false,
195+
});
162196

163197
expect(() => {
164198
render(<DevOverlay />);
165199
}).not.toThrow();
166200
});
167201

168202
it('速度がnullの場合に0km/hを表示する', () => {
169-
mockUseAtomValue.mockReset();
170-
mockUseAtomValue
171-
.mockReturnValueOnce({
203+
setupAtomValues({
204+
location: {
172205
coords: { speed: null, accuracy: 15 },
173-
}) // locationAtom
174-
.mockReturnValueOnce([]) // accuracyHistoryAtom
175-
.mockReturnValue(false); // backgroundLocationTrackingAtom
206+
},
207+
accuracyHistory: [],
208+
backgroundLocationTracking: false,
209+
});
176210

177-
const { getByText } = render(<DevOverlay />);
178-
expect(getByText('Speed: 0km/h')).toBeTruthy();
211+
const { getByTestId } = render(<DevOverlay />);
212+
expect(getByTestId('dev-overlay-speed-value')).toHaveTextContent('0km/h');
179213
});
180214

181215
it('速度が負の値の場合に0km/hを表示する', () => {
182-
mockUseAtomValue.mockReset();
183-
mockUseAtomValue
184-
.mockReturnValueOnce({
216+
setupAtomValues({
217+
location: {
185218
coords: { speed: -5, accuracy: 15 },
186-
}) // locationAtom
187-
.mockReturnValueOnce([]) // accuracyHistoryAtom
188-
.mockReturnValue(false); // backgroundLocationTrackingAtom
219+
},
220+
accuracyHistory: [],
221+
backgroundLocationTracking: false,
222+
});
189223

190-
const { getByText } = render(<DevOverlay />);
191-
expect(getByText('Speed: 0km/h')).toBeTruthy();
224+
const { getByTestId } = render(<DevOverlay />);
225+
expect(getByTestId('dev-overlay-speed-value')).toHaveTextContent('0km/h');
192226
});
193227

194228
it('精度がnullの場合に空文字を表示する', () => {
195-
mockUseAtomValue.mockReset();
196-
mockUseAtomValue
197-
.mockReturnValueOnce({
229+
setupAtomValues({
230+
location: {
198231
coords: { speed: 10, accuracy: null },
199-
}) // locationAtom
200-
.mockReturnValueOnce([]) // accuracyHistoryAtom
201-
.mockReturnValue(false); // backgroundLocationTrackingAtom
232+
},
233+
accuracyHistory: [],
234+
backgroundLocationTracking: false,
235+
});
202236

203-
const { getByText } = render(<DevOverlay />);
204-
expect(getByText('Accuracy: m')).toBeTruthy();
237+
const { getByTestId } = render(<DevOverlay />);
238+
expect(getByTestId('dev-overlay-accuracy-value')).toHaveTextContent('--');
205239
});
206240

207241
it('accuracyHistoryが空配列の場合にクラッシュしない', () => {
208-
mockUseAtomValue.mockReset();
209-
mockUseAtomValue
210-
.mockReturnValueOnce({
242+
setupAtomValues({
243+
location: {
211244
coords: { speed: 10, accuracy: 15 },
212-
}) // locationAtom
213-
.mockReturnValueOnce([]) // accuracyHistoryAtom
214-
.mockReturnValue(false); // backgroundLocationTrackingAtom
245+
},
246+
accuracyHistory: [],
247+
backgroundLocationTracking: false,
248+
});
215249

216250
expect(() => {
217251
render(<DevOverlay />);
218252
}).not.toThrow();
219253
});
220254

221255
it('accuracyHistoryがnullの場合にクラッシュしない', () => {
222-
mockUseAtomValue.mockReset();
223-
mockUseAtomValue
224-
.mockReturnValueOnce({
256+
setupAtomValues({
257+
location: {
225258
coords: { speed: 10, accuracy: 15 },
226-
}) // locationAtom
227-
.mockReturnValue(null); // accuracyHistoryAtom
259+
},
260+
accuracyHistory: null,
261+
backgroundLocationTracking: false,
262+
});
228263

229264
expect(() => {
230265
render(<DevOverlay />);
@@ -234,51 +269,54 @@ describe('DevOverlay', () => {
234269
it('次の駅までの距離が0の場合に適切に表示する', () => {
235270
mockUseDistanceToNextStation.mockReturnValue(0);
236271

237-
const { getByText } = render(<DevOverlay />);
238-
expect(getByText('Next:')).toBeTruthy();
272+
const { getByText, getByTestId } = render(<DevOverlay />);
273+
expect(getByText('NEXT TARGET')).toBeTruthy();
274+
expect(getByTestId('dev-overlay-next-value')).toHaveTextContent('--');
239275
});
240276

241277
it('次の駅情報がundefinedの場合に距離のみ表示する', () => {
242278
mockUseNextStation.mockReturnValue(undefined);
243279

244-
const { getByText } = render(<DevOverlay />);
245-
expect(getByText(/Next: 500m$/)).toBeTruthy();
280+
const { getByTestId } = render(<DevOverlay />);
281+
expect(getByTestId('dev-overlay-next-value')).toHaveTextContent('500m');
246282
});
247283

248284
it('次の駅情報と距離の両方がundefined/0の場合', () => {
249285
mockUseDistanceToNextStation.mockReturnValue(0);
250286
mockUseNextStation.mockReturnValue(undefined);
251287

252-
const { getByText } = render(<DevOverlay />);
253-
expect(getByText('Next:')).toBeTruthy();
288+
const { getByTestId } = render(<DevOverlay />);
289+
expect(getByTestId('dev-overlay-next-value')).toHaveTextContent('--');
254290
});
255291
});
256292

257293
describe('速度計算のロジック', () => {
258294
it('速度が0の場合に0km/hを表示する', () => {
259-
mockUseAtomValue.mockReset();
260-
mockUseAtomValue
261-
.mockReturnValueOnce({
295+
setupAtomValues({
296+
location: {
262297
coords: { speed: 0, accuracy: 15 },
263-
}) // locationAtom
264-
.mockReturnValueOnce([]) // accuracyHistoryAtom
265-
.mockReturnValue(false); // backgroundLocationTrackingAtom
298+
},
299+
accuracyHistory: [],
300+
backgroundLocationTracking: false,
301+
});
266302

267-
const { getByText } = render(<DevOverlay />);
268-
expect(getByText('Speed: 0km/h')).toBeTruthy();
303+
const { getByTestId } = render(<DevOverlay />);
304+
expect(getByTestId('dev-overlay-speed-value')).toHaveTextContent('0km/h');
269305
});
270306

271307
it('速度が正の小数値の場合に正しく変換する', () => {
272-
mockUseAtomValue.mockReset();
273-
mockUseAtomValue
274-
.mockReturnValueOnce({
275-
coords: { speed: 13.89, accuracy: 15 }, // 約50 km/h
276-
}) // locationAtom
277-
.mockReturnValueOnce([]) // accuracyHistoryAtom
278-
.mockReturnValue(false); // backgroundLocationTrackingAtom
279-
280-
const { getByText } = render(<DevOverlay />);
281-
expect(getByText('Speed: 50km/h')).toBeTruthy();
308+
setupAtomValues({
309+
location: {
310+
coords: { speed: 13.89, accuracy: 15 },
311+
},
312+
accuracyHistory: [],
313+
backgroundLocationTracking: false,
314+
});
315+
316+
const { getByTestId } = render(<DevOverlay />);
317+
expect(getByTestId('dev-overlay-speed-value')).toHaveTextContent(
318+
'50km/h'
319+
);
282320
});
283321
});
284322
});

0 commit comments

Comments
 (0)