Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions react-app/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
root: true,
env: { browser: true, es2020: true, node: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', 'dev-dist', '.eslintrc.cjs', 'playwright-report', 'test-results'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
};
10 changes: 10 additions & 0 deletions react-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
dist
dev-dist
*.local
.DS_Store

# test / e2e output
coverage
playwright-report
test-results
121 changes: 121 additions & 0 deletions react-app/e2e/app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { test, expect, Page } from '@playwright/test';

const feed = [
{
id: 1,
title: 'Ask HN: What are you working on?',
points: 120,
user: 'alice',
time: 0,
time_ago: '2 hours ago',
type: 'ask',
url: 'item?id=1',
domain: '',
comments_count: 2,
},
{
id: 2,
title: 'A very interesting external article',
points: 88,
user: 'bob',
time: 0,
time_ago: '3 hours ago',
type: 'link',
url: 'https://example.com/article',
domain: 'example.com',
comments_count: 5,
},
];

const item = {
id: 1,
title: 'Ask HN: What are you working on?',
points: 120,
user: 'alice',
time: 0,
time_ago: '2 hours ago',
type: 'ask',
url: 'item?id=1',
domain: '',
content: '<p>Share your current project.</p>',
comments_count: 2,
comments: [
{
id: 10,
level: 0,
user: 'carol',
time: 0,
time_ago: '1 hour ago',
content: '<p>Working on a React migration.</p>',
deleted: false,
comments: [],
},
],
};

const user = {
id: 'alice',
created: 'March 15, 2015',
karma: 4321,
about: '<p>Hello from Alice</p>',
};

async function mockApi(page: Page) {
await page.route('**/node-hnapi.herokuapp.com/**', async (route) => {
const url = route.request().url();
let body: unknown = [];
if (url.includes('/item/1')) {
body = item;
} else if (url.includes('/user/alice')) {
body = user;
} else {
body = feed;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(body),
});
});
}

test.beforeEach(async ({ page }) => {
await mockApi(page);
});

test('redirects the root to /news/1 and renders the feed', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveURL(/\/news\/1$/);
await expect(page.getByText('Ask HN: What are you working on?')).toBeVisible();
await expect(page.getByText('A very interesting external article')).toBeVisible();
});

test('navigates to an item detail with comments', async ({ page }) => {
await page.goto('/news/1');
await page.getByRole('link', { name: 'Ask HN: What are you working on?' }).first().click();
await expect(page).toHaveURL(/\/item\/1$/);
await expect(page.getByText('Share your current project.')).toBeVisible();
await expect(page.getByText('Working on a React migration.')).toBeVisible();
});

test('navigates to a user profile', async ({ page }) => {
await page.goto('/news/1');
await page.getByRole('link', { name: 'alice' }).first().click();
await expect(page).toHaveURL(/\/user\/alice$/);
await expect(page.getByText('Created March 15, 2015')).toBeVisible();
await expect(page.getByText('Hello from Alice')).toBeVisible();
});

test('toggles a theme in Settings and updates the wrapper class', async ({ page }) => {
await page.goto('/news/1');

// Default theme wrapper is present.
await expect(page.locator('div.default .wrapper')).toHaveCount(1);

// Open settings via the cog icon and choose the Night theme.
await page.getByAltText('Settings').click();
await page.getByLabel('Night').check();

await expect(page.locator('div.night .wrapper')).toHaveCount(1);
await expect(page.locator('div.default .wrapper')).toHaveCount(0);
});
66 changes: 66 additions & 0 deletions react-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular 2 HN</title>

<meta name="description" content="A Hacker News client built with React, Vite and TypeScript"/>

<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@hdjirdeh">
<meta name="twitter:title" content="Angular 2 HN">
<meta name="twitter:description" content="A Hacker News client built with React, Vite and TypeScript">
<meta name="twitter:creator" content="@hdjirdeh">
<meta name="twitter:image" content="assets/images/logo.svg">

<meta property="og:title" content="Angular 2 HN"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://angular2-hn.firebaseapp.com/"/>
<meta property="og:image" content="assets/images/logo.svg"/>
<meta property="og:description" content="A Hacker News client built with React, Vite and TypeScript"/>
<meta property="og:site_name" content="Angular 2 HN"/>
<meta property="fb:admins" content="10202985971279760"/>

<meta name="viewport" content="width=device-width, initial-scale=1">

<meta name="theme-color" content="#b92b27">

<meta name="msapplication-TileColor" content="#b92b27">
<meta name="msapplication-TileImage" content="assets/icons/mstile-150x150.png">
<meta name="msapplication-config" content="assets/icons/browserconfig.xml">

<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">

<link rel="mask-icon" href="assets/icons/safari-pinned-tab.svg" color="#b92b27">

<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" href="/assets/icons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/assets/icons/favicon-16x16.png" sizes="16x16">

<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="120x120" href="/assets/icons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="/assets/icons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon-180x180.png">
<style>
#skip a { position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden; }
#skip a:focus { position:static; width:auto; height:auto; }
</style>
</head>
<body>
<div id="skip"><a href="#content">skip to navigation</a></div>
<div id="root"></div>

<div class="app-loader" id="content">
<img class="logo" src="/assets/images/logo.svg" alt="Logo">

<noscript>
<h4 style="font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica;">
Sorry, JavaScript needs to be enabled in order to run this application.
</h4>
</noscript>
</div>

<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading