Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bunch of fixes and improvements #10

Merged
merged 27 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f33f124
Add redirect error boundary
kasperpeulen Apr 18, 2024
d1095ea
Use new canary and add more stories
kasperpeulen Apr 19, 2024
939723e
Use t3.gg convention
kasperpeulen Apr 19, 2024
5281cab
Clean up some stories
kasperpeulen Apr 29, 2024
ee2ba5a
Fix
kasperpeulen Apr 30, 2024
f1302e0
Refactor code and fix bugs across multiple files
kasperpeulen May 3, 2024
f89631e
Fix for number ids
kasperpeulen May 3, 2024
937e960
Fix test
kasperpeulen May 3, 2024
07f224a
Add back deleted story
kasperpeulen May 3, 2024
145dac2
Merge remote-tracking branch 'refs/remotes/origin/main' into kasper/r…
kasperpeulen May 15, 2024
50a8505
Update to 8.1.0
kasperpeulen May 15, 2024
cb40a03
Fix error
kasperpeulen May 15, 2024
74551ce
Fix more merge conflicts
kasperpeulen May 15, 2024
ba46016
Fix stories
kasperpeulen May 15, 2024
acf38ae
Fix some type issues
kasperpeulen May 15, 2024
be0769e
Use MSW with parameters instead
kasperpeulen May 15, 2024
e54c834
Fix dates to be in the past
kasperpeulen May 15, 2024
38d635c
Add back babelrc
kasperpeulen May 15, 2024
9ed099a
Fix flaky test
kasperpeulen May 15, 2024
c777b40
Fix bug saving a new note
kasperpeulen May 15, 2024
a3353a6
Create expectRedirect test utility
kasperpeulen May 15, 2024
2a1c3b3
Fix redirecting logic
kasperpeulen May 15, 2024
a3d3b33
Address review
kasperpeulen May 15, 2024
cd83be6
Put back env example
kasperpeulen May 15, 2024
d8a1580
Fix edit mode flow
kasperpeulen May 15, 2024
ec7bdc9
Fix unreadable Prisma errors
kasperpeulen May 15, 2024
9eee515
Expect redirect urls
kasperpeulen May 15, 2024
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
2 changes: 1 addition & 1 deletion .storybook/decorators.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Decorator } from '@storybook/react'
import { Layout } from 'app/layout'
import { Layout } from '#app/layout'

export const PageDecorator: Decorator = (Story) => {
return (
Expand Down
1 change: 0 additions & 1 deletion .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { StorybookConfig } from '@storybook/nextjs'
import * as path from 'node:path'

const config: StorybookConfig = {
stories: ['../docs/**/*.mdx', '../**/*.stories.@(js|jsx|mjs|ts|tsx)'],
Expand Down
22 changes: 14 additions & 8 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import '../app/style.css'
import { resetMockDB } from '#lib/db.mock'
import type { Preview } from '@storybook/react'
import { onMockCall } from '@storybook/test'
import MockDate from 'mockdate'
import { initialize, mswLoader } from 'msw-storybook-addon'
import * as MockDate from 'mockdate'
import { initializeDB } from '#lib/db.mock'

onMockCall((spy, args) => {
console.log(spy.name, args)
})
initialize({ onUnhandledRequest: 'bypass' })

const preview: Preview = {
parameters: {
Expand All @@ -16,11 +14,19 @@ const preview: Preview = {
date: /Date$/i,
},
},
test: {
// This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058
// In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown.
dangerouslyIgnoreUnhandledErrors: true,
},
nextjs: { appDirectory: true },
},
loaders: [mswLoader],
beforeEach() {
MockDate.set('2024-05-04T14:00:00.000Z')
resetMockDB()
// Fixed dates for consistent screenshots
MockDate.set('2024-04-18T12:24:02Z')
// reset the database to avoid hanging state between stories
initializeDB()
},
}

Expand Down
30 changes: 17 additions & 13 deletions .storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import { TestRunnerConfig, getStoryContext } from '@storybook/test-runner';
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
import { TestRunnerConfig, getStoryContext } from '@storybook/test-runner'
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport'

const DEFAULT_VIEWPORT_SIZE = { width: 1280, height: 720 };
const DEFAULT_VIEWPORT_SIZE = { width: 1280, height: 720 }

const config: TestRunnerConfig = {
async preVisit(page, story) {
const context = await getStoryContext(page, story);
const viewportName = context.parameters?.viewport?.defaultViewport;
const viewportParameter = MINIMAL_VIEWPORTS[viewportName];
const context = await getStoryContext(page, story)
const viewportName = context.parameters?.viewport?.defaultViewport
const viewportParameter = MINIMAL_VIEWPORTS[viewportName]

if (viewportParameter) {
if (
viewportParameter &&
viewportParameter.styles &&
typeof viewportParameter.styles === 'object'
) {
const viewportSize = Object.entries(viewportParameter.styles).reduce(
(acc, [screen, size]) => ({
...acc,
// make sure your viewport config in Storybook only uses numbers, not percentages
[screen]: parseInt(size),
}),
{}
);
{} as { width: number; height: number },
)

page.setViewportSize(viewportSize);
page.setViewportSize(viewportSize)
} else {
page.setViewportSize(DEFAULT_VIEWPORT_SIZE);
page.setViewportSize(DEFAULT_VIEWPORT_SIZE)
}
},
};
export default config;
}
export default config
1 change: 1 addition & 0 deletions app/actions.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ import * as actions from './actions'
export const saveNote = fn(actions.saveNote).mockName('saveNote')
export const deleteNote = fn(actions.deleteNote).mockName('deleteNote')
export const logout = fn(actions.logout).mockName('logout')
export const login = fn(actions.login).mockName('login')
25 changes: 14 additions & 11 deletions app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { redirect } from 'next/navigation'
import { getUserFromSession } from '#lib/session'

export async function saveNote(
noteId: string | undefined,
noteId: number | undefined,
title: string,
body: string,
) {
Expand All @@ -16,28 +16,25 @@ export async function saveNote(
if (!user) {
redirect('/')
}

if (!noteId) {
noteId = Date.now().toString()
}

const payload = {
id: noteId,
title: title.slice(0, 255),
body: body.slice(0, 2048),
createdBy: user,
}

await db.note.upsert({
if (!noteId) {
const newNote = await db.note.create({ data: payload })
redirect(`/note/${newNote.id}`)
}
await db.note.update({
where: { id: noteId },
update: payload,
create: payload,
data: payload,
})

redirect(`/note/${noteId}`)
}

export async function deleteNote(noteId: string) {
export async function deleteNote(noteId: number) {
await db.note.delete({
where: {
id: noteId,
Expand All @@ -53,3 +50,9 @@ export async function logout() {

redirect('/')
}

export async function login() {
redirect(
`https://github.com/login/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_KEY}&allow_signup=false`,
)
}
57 changes: 57 additions & 0 deletions app/auth/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { redirect } from 'next/navigation'
import { cookies } from 'next/headers'
import { createUserCookie, userCookieKey } from '#lib/session'

const CLIENT_ID = process.env.OAUTH_CLIENT_KEY
const CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET

export async function GET(request: Request) {
const code = new URL(request.url).searchParams.get('code')

let token = ''
try {
const data = await (
await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code,
}),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
).json()

const accessToken = data.access_token

// Let's also fetch the user info and store it in the session.
if (accessToken) {
const userInfo = await (
await fetch('https://api.github.com/user', {
method: 'GET',
headers: {
Authorization: `token ${accessToken}`,
Accept: 'application/json',
},
})
).json()

token = userInfo.login
}
} catch (err: any) {
console.error(err)
redirect('/')
}

if (!token) {
console.error('Github authorization failed')
redirect('/')
}

const cookieValue = await createUserCookie(token)
cookies().set(userCookieKey, `${cookieValue}; Secure; HttpOnly`)
redirect('/')
}
83 changes: 72 additions & 11 deletions app/note/[id]/page.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
import { Meta, StoryObj } from '@storybook/react'
import { useSearchParams } from '@storybook/nextjs/navigation.mock'
import { cookies } from '@storybook/nextjs/headers.mock'
import Page from '#app/note/[id]/page'
import { db } from '#lib/db'
import { http } from 'msw'
import { expect, userEvent, waitFor, within } from '@storybook/test'
import Page from './page'
import { db, initializeDB } from '#lib/db.mock'
import { createUserCookie, userCookieKey } from '#lib/session'
import { PageDecorator } from '#.storybook/decorators'
import { login } from '#app/actions.mock'
import * as auth from '#app/auth/route'

const meta = {
component: Page,
parameters: { layout: 'fullscreen' },
decorators: [PageDecorator],
async beforeEach() {
await db.note.create({
data: {
id: '1',
title: 'Module mocking in Storybook?',
body: "Yup, that's a thing now! 🎉",
createdBy: 'storybookjs',
},
})
await db.note.create({
data: {
id: '2',
title: 'RSC support as well??',
body: 'RSC is pretty cool, even cooler that Storybook supports it!',
createdBy: 'storybookjs',
},
})
},
args: {
params: { id: '2' },
parameters: {
layout: 'fullscreen',
nextjs: {
navigation: {
pathname: '/note/[id]',
query: { id: '1' },
},
},
},
args: { params: { id: '1' } },
} satisfies Meta<typeof Page>

export default meta
Expand All @@ -45,14 +52,68 @@ export const LoggedIn: Story = {

export const NotLoggedIn: Story = {}

export const WithSearchFilter: Story = {
export const LoginShouldGetOAuthTokenAndSetCookie: Story = {
parameters: {
msw: {
// Mock out OAUTH
handlers: [
http.post(
'https://github.com/login/oauth/access_token',
async ({ request }) => {
let json = (await request.json()) as any
return Response.json({ access_token: json.code })
},
),
http.get('https://api.github.com/user', async ({ request }) =>
Response.json({
login: request.headers.get('Authorization')?.replace('token ', ''),
}),
),
],
},
},
beforeEach() {
useSearchParams.mockReturnValue({ get: () => 'RSC' })
// Point the login implementation to the endpoint github would have redirected too.
login.mockImplementation(async () => {
return await auth.GET(new Request('/auth?code=storybookjs'))
})
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await expect(cookies().get(userCookieKey)?.value).toBeUndefined()
await userEvent.click(
await canvas.findByRole('menuitem', { name: /login to add/i }),
)
await waitFor(async () => {
await expect(cookies().get(userCookieKey)?.value).toContain('storybookjs')
})
},
}

export const LogoutShouldDeleteCookie: Story = {
async beforeEach() {
cookies().set(userCookieKey, await createUserCookie('storybookjs'))
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await expect(cookies().get(userCookieKey)?.value).toContain('storybookjs')
await userEvent.click(await canvas.findByRole('button', { name: 'logout' }))
await expect(cookies().get(userCookieKey)).toBeUndefined()
},
}

export const SearchInputShouldFilterNotes: Story = {
parameters: {
nextjs: {
navigation: {
query: { q: 'RSC' },
},
},
},
}

export const EmptyState: Story = {
async beforeEach() {
await db.note.deleteMany()
initializeDB({}) // init an empty DB
},
}
11 changes: 2 additions & 9 deletions app/note/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,15 @@ import NoteUI from '#components/note-ui'
import { db } from '#lib/db'

export const metadata = {
robots: {
index: false,
},
robots: { index: false },
}

type Props = {
params: { id: string }
}

export default async function Page({ params }: Props) {
const note = await db.note.findUnique({
where: {
id: params.id,
},
})

const note = await db.note.findUnique({ where: { id: Number(params.id) } })
if (note === null) {
return (
<div className="note--empty-state">
Expand Down
Loading