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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ gem 'puma', require: false

group :development do
gem 'byebug'
gem 'irb', require: false
gem 'rake', require: false
gem 'rubocop', require: false
gem 'rubocop-performance', require: false
Expand Down
27 changes: 27 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ GEM
bigdecimal
rexml
crass (1.0.6)
date (3.5.1)
diff-lcs (1.6.2)
docile (1.4.1)
drb (2.2.3)
Expand Down Expand Up @@ -144,6 +145,7 @@ GEM
dry-initializer (~> 3.2)
dry-schema (~> 1.14)
zeitwerk (~> 2.6)
erb (6.0.2)
erubi (1.13.1)
faraday (2.14.1)
faraday-net_http (>= 2.0, < 3.5)
Expand All @@ -167,6 +169,11 @@ GEM
io-endpoint (0.17.2)
io-event (1.14.5)
io-stream (0.11.1)
irb (1.17.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.19.3)
json-schema (6.2.0)
addressable (~> 2.8)
Expand Down Expand Up @@ -212,6 +219,9 @@ GEM
parser (3.3.10.2)
ast (~> 2.4.1)
racc
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
prism (1.9.0)
protocol-hpack (1.5.1)
protocol-http (0.60.0)
Expand All @@ -227,6 +237,9 @@ GEM
protocol-url (0.4.0)
protocol-websocket (0.20.2)
protocol-http (~> 0.2)
psych (5.3.1)
date
stringio
public_suffix (7.0.5)
puma (7.2.0)
nio4r (~> 2.0)
Expand Down Expand Up @@ -258,6 +271,10 @@ GEM
rbs (3.10.3)
logger
tsort
rdoc (7.2.0)
erb
psych (>= 4.0.0)
tsort
regexp_parser (2.11.3)
reline (0.6.3)
io-console (~> 0.5)
Expand Down Expand Up @@ -335,6 +352,7 @@ GEM
simplecov_json_formatter (0.1.4)
ssrf_filter (1.3.0)
stackprof (0.2.28)
stringio (3.2.0)
thor (1.5.0)
traces (0.18.2)
tsort (0.2.0)
Expand Down Expand Up @@ -369,6 +387,7 @@ DEPENDENCIES
climate_control
html2rss!
html2rss-configs!
irb
parallel
puma
rack-cache
Expand Down Expand Up @@ -414,6 +433,7 @@ CHECKSUMS
console (1.34.3) sha256=869fbd74697efc4c606f102d2812b0b008e4e7fd738a91c591e8577140ec0dcc
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
Expand All @@ -425,6 +445,7 @@ CHECKSUMS
dry-schema (1.16.0) sha256=cd3aaeabc0f1af66ec82a29096d4c4fb92a0a58b9dae29a22b1bbceb78985727
dry-types (1.9.1) sha256=baebeecdb9f8395d6c9d227b62011279440943e3ef2468fe8ccc1ba11467f178
dry-validation (1.11.1) sha256=70900bb5a2d911c8aab566d3e360c6bff389b8bf92ea8e04885ce51c41ff8085
erb (6.0.2) sha256=9fe6264d44f79422c87490a1558479bd0e7dad4dd0e317656e67ea3077b5242b
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c
faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad
Expand All @@ -441,6 +462,7 @@ CHECKSUMS
io-endpoint (0.17.2) sha256=3feaf766c116b35839c11fac68b6aaadc47887bb488902a57bf8e1d288fb3338
io-event (1.14.5) sha256=68ac367032a3873416dc2e0b67332dfaf2e23b65b58e6465d301c7e5cd9163b1
io-stream (0.11.1) sha256=fa5f551fcff99581c1757b9d1cee2c37b124f07d2ca4f40b756a05ab9bd21b87
irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
kramdown (2.5.2) sha256=1ba542204c66b6f9111ff00dcc26075b95b220b07f2905d8261740c82f7f02fa
Expand All @@ -465,6 +487,8 @@ CHECKSUMS
nokogiri (1.19.2-x86_64-linux-musl) sha256=93128448e61a9383a30baef041bf1f5817e22f297a1d400521e90294445069a8
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
protocol-hpack (1.5.1) sha256=6feca238b8078da1cd295677d6f306c6001af92d75fe0643d33e6956cbc3ad91
protocol-http (0.60.0) sha256=ca1354947676d663b6f23c49654aee464288774e7867c4a6e406fecce9691cec
Expand All @@ -473,6 +497,7 @@ CHECKSUMS
protocol-rack (0.22.0) sha256=b7c49c0b597ca2c6d20f8bcd746c4415a1b750eacfbe64f828e780c978a4293d
protocol-url (0.4.0) sha256=64d4c03b6b51ad815ac6fdaf77a1d91e5baf9220d26becb846c5459dacdea9e1
protocol-websocket (0.20.2) sha256=c41d93c35fba5dae85375c597f76975f3dbd75d8c5b2f21b33dab4dc22a5a511
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8
puppeteer-ruby (0.51.0) sha256=8a7637963f8cd5b88416dd8c669a3ec2fe40a42cda2449539d75525a4da2f233
Expand All @@ -487,6 +512,7 @@ CHECKSUMS
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
rbs (3.10.3) sha256=70627f3919016134d554e6c99195552ae3ef6020fe034c8e983facc9c192daa6
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
reverse_markdown (3.0.2) sha256=818ebb92ce39dbb1a291690dd1ec9a6d62530d4725296b17e9c8f668f9a5b8af
Expand Down Expand Up @@ -515,6 +541,7 @@ CHECKSUMS
simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
ssrf_filter (1.3.0) sha256=66882d7de7d09c019098d6d7372412950ae184ebbc7c51478002058307aba6f2
stackprof (0.2.28) sha256=4ec2ace02f386012b40ca20ef80c030ad711831f59511da12e83b34efb0f9a04
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
traces (0.18.2) sha256=80f1649cb4daace1d7174b81f3b3b7427af0b93047759ba349960cb8f315e214
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/__tests__/App.contract.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('App contract', () => {
})
);
}),
http.get('/api/v1/feeds/generated-token', ({ request }) => {
http.get('/api/v1/feeds/generated-token.json', ({ request }) => {
expect(request.headers.get('accept')).toBe('application/feed+json');

return HttpResponse.json(
Expand Down
44 changes: 32 additions & 12 deletions frontend/src/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,21 +206,22 @@ describe('App', () => {
});

it('renders the result panel when a feed is available', async () => {
vi.spyOn(window, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ items: [] }),
} as Response);

mockUseFeedConversion.mockReturnValue({
isConverting: false,
result: {
id: 'feed-123',
name: 'Example Feed',
url: 'https://example.com/articles',
strategy: 'faraday',
feed_token: 'example-token',
public_url: '/api/v1/feeds/example-token',
json_public_url: '/api/v1/feeds/example-token.json',
feed: {
id: 'feed-123',
name: 'Example Feed',
url: 'https://example.com/articles',
strategy: 'faraday',
feed_token: 'example-token',
public_url: '/api/v1/feeds/example-token',
json_public_url: '/api/v1/feeds/example-token.json',
},
preview: {
items: [],
error: 'Preview unavailable right now.',
},
},
error: null,
convertFeed: mockConvertFeed,
Expand All @@ -233,6 +234,7 @@ describe('App', () => {
expect(screen.getByRole('button', { name: 'Create another feed' })).toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Bookmarklet' })).not.toBeInTheDocument();
expect(screen.getByText('Example Feed')).toBeInTheDocument();
expect(screen.getByText('Preview unavailable right now.')).toBeInTheDocument();
});

it('surfaces conversion errors to the user', () => {
Expand All @@ -250,6 +252,24 @@ describe('App', () => {
expect(screen.getByText('Access denied')).toBeInTheDocument();
});

it('shows an explicit loading notice while feed creation is still resolving preview state', () => {
mockUseFeedConversion.mockReturnValue({
isConverting: true,
result: null,
error: null,
convertFeed: mockConvertFeed,
clearError: mockClearConversionError,
clearResult: mockClearResult,
});

render(<App />);

expect(screen.getByText('Preparing feed')).toBeInTheDocument();
expect(
screen.getByText('Creating the feed and loading its preview before showing the result.')
).toBeInTheDocument();
});

it('clears stored token from instance info', () => {
mockUseAccessToken.mockReturnValue({
token: 'saved-token',
Expand Down
77 changes: 38 additions & 39 deletions frontend/src/__tests__/ResultDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,41 @@ import { ResultDisplay } from '../components/ResultDisplay';
describe('ResultDisplay', () => {
const mockOnCreateAnother = vi.fn();
const mockResult = {
id: 'test-id',
name: 'Test Feed',
url: 'https://example.com',
strategy: 'faraday',
feed_token: 'test-feed-token',
public_url: 'https://example.com/feed.xml',
json_public_url: 'https://example.com/feed.json',
feed: {
id: 'test-id',
name: 'Test Feed',
url: 'https://example.com',
strategy: 'faraday',
feed_token: 'test-feed-token',
public_url: 'https://example.com/feed.xml',
json_public_url: 'https://example.com/feed.json',
},
preview: {
items: [
{
title: 'Item One',
excerpt: 'First preview item with markup.',
url: 'https://example.com/item-one',
publishedLabel: 'Jan 1, 2024',
},
{
title: '56 points by canpan 1 hour ago | hide | 18 comments',
excerpt: '',
publishedLabel: 'Jan 2, 2024',
},
{
title: 'Item Two',
excerpt: '',
url: 'https://example.com/item-two',
publishedLabel: 'Jan 3, 2024',
},
],
error: null,
},
};

beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(window, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({
items: [
{
title: 'Item One',
content_text: '<p>First preview item with <strong>markup</strong>.</p>',
url: 'https://example.com/item-one',
date_published: '2024-01-01T00:00:00Z',
},
{
content_text: '56 points by canpan 1 hour ago | hide | 18&nbsp;comments',
date_published: '2024-01-02T00:00:00Z',
},
{
content_text: '2. Item Two ( example.com )',
url: 'https://example.com/item-two',
date_published: '2024-01-03T00:00:00Z',
},
],
}),
} as Response);
});

it('renders the success state actions and richer preview cards', async () => {
Expand All @@ -60,18 +62,15 @@ describe('ResultDisplay', () => {
expect(screen.getByText('Item Two')).toBeInTheDocument();
expect(screen.getByText('Latest items from this feed')).toBeInTheDocument();
});
expect(window.fetch).toHaveBeenCalledWith('https://example.com/feed.xml', {
headers: { Accept: 'application/feed+json' },
});
});

it('surfaces preview fetch failures as a result-state message', async () => {
vi.mocked(window.fetch).mockResolvedValueOnce({
ok: false,
json: async () => ({}),
} as Response);

render(<ResultDisplay result={mockResult} onCreateAnother={mockOnCreateAnother} />);
it('surfaces preview failures as a result-state message', async () => {
render(
<ResultDisplay
result={{ ...mockResult, preview: { items: [], error: 'Preview unavailable right now.' } }}
onCreateAnother={mockOnCreateAnother}
/>
);

await waitFor(() => {
expect(screen.getByText('Preview unavailable right now.')).toBeInTheDocument();
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import '@testing-library/jest-dom';
import { afterAll, afterEach, beforeAll, beforeEach, vi } from 'vitest';
import { cleanup } from '@testing-library/preact';
import { server } from './mocks/server';

let server: typeof import('./mocks/server').server;

// Mock window and document for tests
Object.defineProperty(window, 'matchMedia', {
Expand Down Expand Up @@ -49,10 +50,16 @@ const session = createStorageMock();
Object.defineProperty(window, 'localStorage', {
value: local.api,
});
Object.defineProperty(globalThis, 'localStorage', {
value: local.api,
});

Object.defineProperty(window, 'sessionStorage', {
value: session.api,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: session.api,
});

beforeEach(() => {
local.store.clear();
Expand Down Expand Up @@ -80,7 +87,10 @@ Object.assign(navigator, {
Element.prototype.scrollIntoView = vi.fn();

// Wire up MSW in node environment
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
beforeAll(async () => {
({ server } = await import('./mocks/server'));
server.listen({ onUnhandledRequest: 'error' });
});
afterEach(() => {
server.resetHandlers();
cleanup();
Expand Down
Loading
Loading