mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Snapshot URL datasets locally on add; preview tabular data as a table
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { RemoteFetchError } from '../infrastructure/remote-data';
|
||||
import { remoteFetchErrorMessage } from './remote-data-errors';
|
||||
|
||||
describe('remoteFetchErrorMessage', () => {
|
||||
test('a network failure hedges offline vs cross-origin and offers the inline fallback', () => {
|
||||
const msg = remoteFetchErrorMessage(new RemoteFetchError('network', 'x'));
|
||||
expect(msg).toMatch(/offline/i);
|
||||
expect(msg).toMatch(/cross-origin/i);
|
||||
expect(msg).toMatch(/paste the data inline/i);
|
||||
});
|
||||
|
||||
test('an HTTP 404 explains "not found" without a raw error code', () => {
|
||||
const msg = remoteFetchErrorMessage(new RemoteFetchError('http', 'x', 404));
|
||||
expect(msg).toMatch(/couldn't find/i);
|
||||
expect(msg).not.toMatch(/404/);
|
||||
});
|
||||
|
||||
test('an HTTP 403 explains refused access', () => {
|
||||
expect(remoteFetchErrorMessage(new RemoteFetchError('http', 'x', 403))).toMatch(
|
||||
/refused access/i,
|
||||
);
|
||||
});
|
||||
|
||||
test('an empty body says it returned no data', () => {
|
||||
expect(remoteFetchErrorMessage(new RemoteFetchError('empty', 'x'))).toMatch(/no data/i);
|
||||
});
|
||||
|
||||
test('the retry recovery points at refreshing, not pasting inline', () => {
|
||||
const msg = remoteFetchErrorMessage(new RemoteFetchError('timeout', 'x'), 'retry');
|
||||
expect(msg).toMatch(/refreshing again/i);
|
||||
expect(msg).not.toMatch(/inline/i);
|
||||
});
|
||||
|
||||
test('an unknown error still produces a sensible fallback message', () => {
|
||||
expect(remoteFetchErrorMessage(new Error('boom'))).toMatch(/couldn't fetch this url/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Translate a remote-data fetch failure (snapshot model — spec §05 → URL datasets)
|
||||
* into the message shown when adding or refreshing a URL dataset.
|
||||
*
|
||||
* Pure and framework-free so the wording is unit-tested directly — the same split
|
||||
* as `storage-errors`. Follows the recorded error-copy resolution (docs/architecture
|
||||
* /10 → error copy): plain language, name what stopped, and — because every one of
|
||||
* these is user-fixable — point at the next step. No error codes in the user-facing
|
||||
* line (GOV.UK / NN/g #9). The recovery clause adapts to where the error shows: the
|
||||
* create/edit form can fall back to pasting inline; Refresh can only retry.
|
||||
*/
|
||||
|
||||
import { RemoteFetchError } from '../infrastructure/remote-data';
|
||||
|
||||
/** Where the error surfaces, which decides the recovery the copy points at. */
|
||||
export type FetchRecovery = 'inline' | 'retry';
|
||||
|
||||
function nextStep(recovery: FetchRecovery): string {
|
||||
return recovery === 'inline'
|
||||
? 'Check the address, or paste the data inline instead.'
|
||||
: 'Check the address and try refreshing again.';
|
||||
}
|
||||
|
||||
function httpCause(status?: number): string {
|
||||
if (status === 404) return "The server couldn't find anything at this URL.";
|
||||
if (status === 401 || status === 403) return 'The server refused access to this URL.';
|
||||
if (status && status >= 500) return 'The server hit an error serving this URL.';
|
||||
return "The server couldn't return this URL.";
|
||||
}
|
||||
|
||||
/**
|
||||
* The user-facing message for a fetch failure. `recovery` defaults to `inline`
|
||||
* (the add/edit form); pass `retry` for the Refresh action, which has no inline
|
||||
* fallback.
|
||||
*/
|
||||
export function remoteFetchErrorMessage(err: unknown, recovery: FetchRecovery = 'inline'): string {
|
||||
const next = nextStep(recovery);
|
||||
if (err instanceof RemoteFetchError) {
|
||||
switch (err.reason) {
|
||||
case 'network':
|
||||
return `Couldn't fetch this URL. Your device may be offline, or the host may block cross-origin requests. ${next}`;
|
||||
case 'http':
|
||||
return `${httpCause(err.status)} ${next}`;
|
||||
case 'empty':
|
||||
return `This URL returned no data. Make sure it points directly at a data file. ${next}`;
|
||||
case 'timeout':
|
||||
return `This URL took too long to respond. ${next}`;
|
||||
}
|
||||
}
|
||||
return `Couldn't fetch this URL. ${next}`;
|
||||
}
|
||||
Reference in New Issue
Block a user