Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 86 additions & 7 deletions frontend/src/__tests__/appViewModel.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { deriveAppViewModel } from '../appViewModel';
import { deriveAppViewModel, getPanelViewState } from '../appViewModel';

const emptyErrors = { url: '', form: '' };

Expand All @@ -9,7 +9,7 @@ describe('deriveAppViewModel', () => {
deriveAppViewModel({
feedFieldErrors: emptyErrors,
isConverting: false,
routeKind: 'create',
route: { kind: 'create' },
tokenError: '',
})
).toEqual({ kind: 'create' });
Expand All @@ -19,7 +19,7 @@ describe('deriveAppViewModel', () => {
const viewModel = deriveAppViewModel({
feedFieldErrors: emptyErrors,
isConverting: false,
routeKind: 'result',
route: { kind: 'result', feedToken: 'token' },
tokenError: '',
result: {
feed: {
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('deriveAppViewModel', () => {
deriveAppViewModel({
feedFieldErrors: emptyErrors,
isConverting: false,
routeKind: 'create',
route: { kind: 'create' },
tokenError: '',
conversionError: {
kind: 'auth',
Expand All @@ -70,7 +70,7 @@ describe('deriveAppViewModel', () => {
deriveAppViewModel({
feedFieldErrors: emptyErrors,
isConverting: true,
routeKind: 'create',
route: { kind: 'create' },
tokenError: '',
})
).toEqual({ kind: 'submitting' });
Expand All @@ -79,7 +79,7 @@ describe('deriveAppViewModel', () => {
deriveAppViewModel({
feedFieldErrors: { url: '', form: 'Bad url' },
isConverting: false,
routeKind: 'create',
route: { kind: 'create' },
tokenError: '',
conversionError: {
kind: 'input',
Expand All @@ -98,9 +98,88 @@ describe('deriveAppViewModel', () => {
deriveAppViewModel({
feedFieldErrors: emptyErrors,
isConverting: true,
routeKind: 'token',
route: { kind: 'token' },
tokenError: '',
})
).toEqual({ kind: 'submitting' });
});
});

describe('getPanelViewState', () => {
it('derives values for create state', () => {
const state = getPanelViewState({ kind: 'create' }, emptyErrors);
expect(state).toEqual({
isTokenPrompt: false,
isConverting: false,
tokenError: '',
errorKind: undefined,
failureMessage: '',
isShowRetryButton: false,
});
});

it('derives values for submitting state', () => {
const state = getPanelViewState({ kind: 'submitting' }, emptyErrors);
expect(state).toEqual({
isTokenPrompt: false,
isConverting: true,
tokenError: '',
errorKind: undefined,
failureMessage: '',
isShowRetryButton: false,
});
});

it('derives values for token_prompt state', () => {
const state = getPanelViewState(
{
kind: 'token_prompt',
tokenError: 'Invalid token',
error: {
kind: 'auth',
code: 'UNAUTHORIZED',
retryable: false,
nextAction: 'enter_token',
retryAction: 'none',
message: 'Access denied',
},
},
emptyErrors
);
expect(state).toEqual({
isTokenPrompt: true,
isConverting: false,
tokenError: 'Invalid token',
errorKind: 'auth',
failureMessage: 'Access denied',
isShowRetryButton: false,
});
});

it('derives values for error state', () => {
const state = getPanelViewState(
{
kind: 'error',
message: 'Something went wrong',
errorKind: 'network',
error: {
kind: 'network',
code: 'TIMEOUT',
retryable: true,
nextAction: 'retry',
retryAction: 'recreate',
message: 'Something went wrong',
},
},
emptyErrors
);
expect(state).toEqual({
isTokenPrompt: false,
isConverting: false,
tokenError: '',
errorKind: 'network',
failureMessage: 'Something went wrong',
isShowRetryButton: true,
});
});
});
48 changes: 44 additions & 4 deletions frontend/src/appViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
FeedPreviewWarning,
FeedRecord,
} from './api/contracts';
import type { AppRoute } from './routes/appRoute';

export type AppViewModel =
| { kind: 'create' }
Expand All @@ -27,7 +28,7 @@ export function deriveAppViewModel({
conversionError,
feedFieldErrors,
isConverting,
routeKind,
route,
tokenError,
tokenStateError,
metadataError,
Expand All @@ -36,7 +37,7 @@ export function deriveAppViewModel({
conversionError?: FeedCreationError;
feedFieldErrors: { url: string; form: string };
isConverting: boolean;
routeKind: 'create' | 'token' | 'result';
route: AppRoute;
tokenError: string;
tokenStateError?: string;
metadataError?: string;
Expand All @@ -49,7 +50,7 @@ export function deriveAppViewModel({
};
}

if (routeKind === 'result' && result) {
if (route.kind === 'result' && result && result.feed.feed_token === route.feedToken) {
return {
kind: 'result',
feed: result.feed,
Expand All @@ -60,7 +61,7 @@ export function deriveAppViewModel({

if (isConverting) return { kind: 'submitting' };

if (routeKind === 'token' || tokenError) {
if (route.kind === 'token' || tokenError) {
return { kind: 'token_prompt', tokenError, error: conversionError };
}

Expand Down Expand Up @@ -88,3 +89,42 @@ export function deriveAppViewModel({

return { kind: 'create' };
}

export interface PanelViewState {
isTokenPrompt: boolean;
isConverting: boolean;
tokenError: string;
errorKind?: FeedCreationError['kind'];
failureMessage: string;
isShowRetryButton: boolean;
}

export function getPanelViewState(
viewModel: AppViewModel,
feedFieldErrors: { form: string }
): PanelViewState {
const isTokenPrompt = viewModel.kind === 'token_prompt';
const isConverting = viewModel.kind === 'submitting';
const conversionError =
viewModel.kind === 'error' || viewModel.kind === 'token_prompt' ? viewModel.error : undefined;
const tokenError = viewModel.kind === 'token_prompt' ? viewModel.tokenError : '';
const errorKind = viewModel.kind === 'error' ? viewModel.errorKind : conversionError?.kind;

const failureMessage =
(viewModel.kind === 'error' ? viewModel.message : undefined) ||
conversionError?.message ||
feedFieldErrors.form;

const isShowRetryButton = Boolean(
conversionError && conversionError.nextAction === 'retry' && conversionError.retryAction !== 'none'
);

return {
isTokenPrompt,
isConverting,
tokenError,
errorKind,
failureMessage,
isShowRetryButton,
};
}
Loading
Loading