Skip to content

Commit

Permalink
Merge branch 'codu-code:develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
petercr authored Nov 7, 2024
2 parents e6883a5 + 94401a6 commit 87038df
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 87 deletions.
2 changes: 0 additions & 2 deletions E2E Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

To run the end-to-end tests using Playwright, you need to configure your environment and follow these steps:



### Session and User Setup

First, you need to add your E2E test user to your locally running database. Do this by running the following script if you haven't already:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
Tuesday, November 5th, 2024 • Niall Maher

# ⚡️ Better Image Loading + React Native's New Architecture

[Read online](https://www.codu.co/letters/is-your-domain-haunted)

My tip of the week is something I just learned about. A property called `fetchpriority` that drastically improved our Core Web Vitals. While most developers know about lazy loading, combining a few loading properties can make a big difference. You can further boost the priority of fetching an image using the `fetchpriority` property.

Here's what it looks like in action:​​​​​​​

```html
rel="preload" as="image" href="hero.webp" fetchpriority="high" > src="hero.webp"
loading="eager" fetchpriority="high" decoding="async" >
```

Let's break down why this works so well:

- The `<link rel="preload">` tells browsers to start downloading the image immediately, even before they discover the tag while parsing HTML
- `fetchpriority="high"` bumps the image to the top of the browser's resource queue, making it download before less critical resources
- `loading="eager"` disables lazy loading for this specific image (crucial for above-the-fold content)
- `decoding="async"` lets the browser optimize image decode timing without blocking other tasks

**Just remember:** use this pattern sparingly for critical above-the-fold images directly impacting LCP. For everything else, stick with regular lazy loading.

And a **Pro tip:** Combine this with the `srcset` attribute to handle responsive images, and you've got a bulletproof image-loading strategy. I wrote a little article on it this week. [Read it here](https://www.codu.co/articles/responsive-images-in-html-using-srcset-lyb6r6r0).

## 📚 This Week's Picks

**[Creating Custom VS Code Snippets for Faster Development](https://www.codu.co/articles/creating-custom-vs-code-snippets-for-faster-development-kgjv1ct)** (4 min)\
Let's explore how to create custom snippets that will boost your productivity.

**[Introduction to C# and .NET](https://www.codu.co/articles/introduction-to-c-and-net-w0hc8gz3)** (4 min)\
What is C#, and what is .NET? The first article in a series Adrián is writing to teach you C#.

**[Enhanced local IDE experience for AWS Lambda developers](https://aws.amazon.com/blogs/compute/introducing-an-enhanced-local-ide-experience-for-aws-lambda-developers/)** (8 min)\
AWS Lambda is introducing an improved local IDE experience to simplify Lambda-based application development. The new features help developers to author, build, debug, test, and deploy Lambda applications more efficiently in their local IDE.

**[25 crazy software bugs explained](https://www.youtube.com/watch?v=Iq_r7IcNmUk)** (video)\
Fireship dives into 25 crazy software bugs that changed the world.

**[React Native - New Architecture is here](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here)** (22 min)\
The 0.76 release blog post shared a list of significant changes in this version. It provides an overview of the New Architecture and how it shapes the future of React Native.

**[Lessons learned from a successful Rust rewrite](https://gaultier.github.io/blog/lessons_learned_from_a_successful_rust_rewrite.html)** (12 min)\
A great deep dive into some important lessons when migrating a C++ codebase to Rust.

## 📖 Book of the Week

**[The Engineering Executive's Primer: Impactful Technical Leadership](https://amzn.to/4fj5bWf)**

This book is essential for technical leaders navigating the complexities of engineering management. Drawing from his extensive experience at companies like Stripe and Uber, Larson offers practical frameworks for scaling engineering organizations, developing talent, and driving technical strategy. What sets this book apart is its focused approach to the unique challenges faced by senior engineering leaders, from managing organizational design to balancing technical debt with business growth. Particularly valuable are the actionable insights on building effective engineering processes and fostering a culture of technical excellence.

Whether you're an engineering executive or aspiring to become one, this book provides the strategic toolkit needed for impactful technical leadership.

## 🛠️ Something Cool

**[Posthog](https://posthog.com/)**

This is not a sponsored post (I am mentioning it because I do see Posthog sponsors a lot of content). I've been using this tool again recently for a couple of projects, and it's just fantastic.

PostHog offers the full package---session recordings, feature flags, A/B testing, and product analytics. Whether you're running early experiments or want to systemize your product decisions, PostHog provides the data and tools needed to make data-driven choices. It has a very generous free tier (which I haven't been lucky enough to hit just yet with any of my side projects).

## 🔗 Quick Links

- [Codú TikTok](https://www.tiktok.com/@codu.co)
- [Hacktoberfest GitHub Issues](https://github.com/codu-code/codu/issues)
- [Our YouTube channel](https://www.youtube.com/@codu)
- [Find us on Twitch](https://www.twitch.tv/codudotco)

**If you have any ideas or feedback, reply to this email.**

Thanks, and stay awesome,

Niall

Founder @ [Codú](https://www.codu.co/?ref=newsletter)
2 changes: 1 addition & 1 deletion app/(editor)/create/[[...paramsArr]]/_client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ const Create = ({ session }: { session: Session | null }) => {
type="button"
className="relative flex w-full focus:outline-none focus:ring-2 focus:ring-pink-300 focus:ring-offset-2 active:hover:bg-neutral-50 disabled:opacity-50"
>
<div className="input-base flex max-w-full flex-1 overflow-hidden border text-left">
<div className="flex w-full max-w-full flex-1 overflow-hidden border px-2 py-2 text-left text-black shadow-sm ring-offset-1 focus:border-pink-500 focus:outline-none focus:ring-2 focus:ring-neutral-300 disabled:opacity-50 dark:border-white dark:bg-black dark:text-white sm:text-sm">
{PREVIEW_URL}
</div>
<div className="absolute bottom-0 right-0 top-0 w-[120px] border border-neutral-300 bg-white px-4 py-2 font-medium text-neutral-600 shadow-sm">
Expand Down
2 changes: 1 addition & 1 deletion components/ArticleMenu/ArticleMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const ArticleMenu = ({
</div>

<button
className="focus-style-rounded rounded-full p-1 hover:bg-neutral-300 dark:hover:bg-neutral-800 lg:mx-auto"
className="rounded-full p-1 hover:bg-neutral-300 focus:outline-none focus:ring-white focus-visible:ring-2 focus-visible:ring-pink-600 focus-visible:ring-offset-pink-600 dark:hover:bg-neutral-800 lg:mx-auto"
aria-label="bookmark-trigger"
onClick={() => {
if (!session) {
Expand Down
2 changes: 1 addition & 1 deletion components/ArticlePreview/ArticlePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const ArticlePreview: NextPage<Props> = ({
<div className="flex gap-x-2">
{showBookmark && (
<button
className="focus-style-rounded rounded-full p-2 hover:bg-neutral-300 dark:hover:bg-neutral-800 lg:mx-auto"
className="rounded-full p-2 hover:bg-neutral-300 focus:outline-none focus:ring-white focus-visible:ring-2 focus-visible:ring-pink-600 focus-visible:ring-offset-pink-600 dark:hover:bg-neutral-800 lg:mx-auto"
onClick={() => {
if (!session) {
return signIn();
Expand Down
6 changes: 5 additions & 1 deletion components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@ const navigation = {
social: [
{
name: "Twitter",
customStyle: "hover:bg-twitter focus:bg-twitter",
href: twitterUrl,
icon: Twitter,
},
{
name: "GitHub",
customStyle: "hover:bg-github focus:bg-github",
href: githubUrl,
icon: Github,
},
{
name: "Discord",
customStyle: "hover:bg-discord focus:bg-discord",
href: discordInviteUrl,
icon: Discord,
},
{
name: "Youtube",
customStyle: "hover:bg-youtube focus:bg-youtube",
href: youtubeUrl,
icon: Youtube,
},
Expand Down Expand Up @@ -77,7 +81,7 @@ const Footer = () => {
href={item.href}
target="_blank"
rel="noopener noreferrer"
className={`focus-style rounded-md p-1 transition-all duration-300 hover:scale-105 hover:text-white hover:brightness-110 focus:scale-105 focus:text-white focus:brightness-110 ${item.name.toLowerCase()}`}
className={`focus-style rounded-md p-1 transition-all duration-300 hover:scale-105 hover:text-white hover:brightness-110 focus:scale-105 focus:text-white focus:brightness-110 ${item.customStyle.toLowerCase()}`}
>
<span className="sr-only">{item.name}</span>
<item.icon className="h-6 w-6" aria-hidden="true" />
Expand Down
18 changes: 8 additions & 10 deletions e2e/articles.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from "playwright/test";
import { randomUUID } from "crypto";
import { loggedInAsUserOne } from "./utils";
import { articleContent, articleExcerpt, loggedInAsUserOne } from "./utils";

test.describe("Unauthenticated Articles Page", () => {
test("Should show popular tags", async ({ page, isMobile }) => {
Expand All @@ -17,10 +17,10 @@ test.describe("Unauthenticated Articles Page", () => {
test("Should be able to navigate directly to an article", async ({
page,
}) => {
await page.goto("http://localhost:3000/articles/e2e-test-slug-eqj0ozor");
await expect(page.getByText("Lorem ipsum dolor sit amet,")).toBeVisible();
await page.goto("http://localhost:3000/articles/e2e-test-slug-published");
await expect(page.getByText(articleExcerpt)).toBeVisible();
await expect(
page.getByRole("heading", { name: "Test Article" }),
page.getByRole("heading", { name: "Published Article" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Written by E2E Test User One" }),
Expand Down Expand Up @@ -223,8 +223,6 @@ test.describe("Authenticated Articles Page", () => {
});

test("Should write and publish an article", async ({ page, isMobile }) => {
const articleContent =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae ipsum id metus vestibulum rutrum eget a diam. Integer eget vulputate risus, ac convallis nulla. Mauris sed augue nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam congue posuere tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ac augue non libero ullamcorper ornare. Ut commodo ligula vitae malesuada maximus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam sagittis justo non justo placerat, a dapibus sapien volutpat. Nullam ullamcorper sodales justo sed.";
const articleTitle = "Lorem Ipsum";
await page.goto("http://localhost:3000");
// Waits for articles to be loaded
Expand Down Expand Up @@ -261,7 +259,7 @@ test.describe("Authenticated Articles Page", () => {
/^http:\/\/localhost:3000\/articles\/lorem-ipsum-.*$/,
);

await expect(page.getByText("Lorem ipsum dolor sit amet,")).toBeVisible();
await expect(page.getByText(articleExcerpt)).toBeVisible();
await expect(
page.getByRole("heading", { name: "Lorem Ipsum" }),
).toBeVisible();
Expand Down Expand Up @@ -291,14 +289,14 @@ test.describe("Authenticated Articles Page", () => {
});

test("Should be able reply to a comment", async ({ page }) => {
await page.goto("http://localhost:3000/articles/e2e-test-slug-eqj0ozor");
await page.goto("http://localhost:3000/articles/e2e-test-slug-published");
const numberOfCommentsIntially = await page
.locator("div")
.filter({ hasText: /^Thanks for the positive feedback!$/ })
.count();
await expect(page.getByText("Lorem ipsum dolor sit amet,")).toBeVisible();
await expect(page.getByText(articleExcerpt)).toBeVisible();
await expect(
page.getByRole("heading", { name: "Test Article" }),
page.getByRole("heading", { name: "Published Article" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Written by E2E Test User One" }),
Expand Down
53 changes: 47 additions & 6 deletions e2e/my-posts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,56 @@
import test from "@playwright/test";
import { loggedInAsUserOne } from "./utils";
import test, { expect } from "@playwright/test";
import { articleExcerpt, loggedInAsUserOne } from "./utils";

test.describe("Unauthenticated my-posts Page", () => {
//
// Replace with tests for unauthenticated users
test("Unauthenticated users should be redirected to get-started page if they access my-posts directly", async ({
page,
}) => {
await page.goto("http://localhost:3000/my-posts");
await page.waitForURL("http://localhost:3000/get-started");
expect(page.url()).toEqual("http://localhost:3000/get-started");
});
});

test.describe("Authenticated my-posts Page", () => {
test.beforeEach(async ({ page }) => {
await loggedInAsUserOne(page);
});
//
// Replace with tests for authenticated users

test("Tabs for different type of posts should be visible", async ({
page,
}) => {
await page.goto("http://localhost:3000/my-posts");

await expect(page.getByRole("link", { name: "Drafts" })).toBeVisible();
await expect(page.getByRole("link", { name: "Scheduled" })).toBeVisible();
await expect(page.getByRole("link", { name: "Published" })).toBeVisible();
});

test("Different article tabs should correctly display articles matching that type", async ({
page,
}) => {
await page.goto("http://localhost:3000/my-posts");

await expect(page.getByRole("link", { name: "Drafts" })).toBeVisible();
await expect(page.getByRole("link", { name: "Scheduled" })).toBeVisible();
await expect(page.getByRole("link", { name: "Published" })).toBeVisible();

await page.getByRole("link", { name: "Drafts" }).click();
await expect(
page.getByRole("heading", { name: "Draft Article" }),
).toBeVisible();
await expect(page.getByText(articleExcerpt)).toBeVisible();

await page.getByRole("link", { name: "Scheduled" }).click();
await expect(
page.getByRole("heading", { name: "Scheduled Article" }),
).toBeVisible();
await expect(page.getByText(articleExcerpt)).toBeVisible();

await page.getByRole("link", { name: "Published" }).click();
await expect(
page.getByRole("heading", { name: "Published Article" }),
).toBeVisible();
await expect(page.getByText(articleExcerpt, { exact: true })).toBeVisible();
});
});
84 changes: 65 additions & 19 deletions e2e/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import { post, comment, session, user } from "@/server/db/schema";
import {
articleContent,
articleExcerpt,
E2E_USER_ONE_EMAIL,
E2E_USER_ONE_ID,
E2E_USER_ONE_SESSION_ID,
Expand All @@ -12,37 +14,81 @@ import {
import { eq } from "drizzle-orm";

export const setup = async () => {
// Dynamically import nanoid
const { nanoid } = await import("nanoid");

const db = drizzle(
postgres("postgresql://postgres:secret@127.0.0.1:5432/postgres"),
);

const addE2EArticleAndComment = async (
authorId: string,
commenterId: string,
) => {
const postId = "1nFnMmN5";
const publishedPostId = nanoid(8);
const scheduledPostId = nanoid(8);
const draftPostId = nanoid(8);
const now = new Date().toISOString();
await db
.insert(post)
.values({
id: postId,
published: now,
excerpt: "Lorem ipsum dolor sit amet",
updatedAt: now,
slug: "e2e-test-slug-eqj0ozor",
likes: 10,
readTimeMins: 3,
title: "Test Article",
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae ipsum id metus vestibulum rutrum eget a diam. Integer eget vulputate risus, ac convallis nulla. Mauris sed augue nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam congue posuere tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ac augue non libero ullamcorper ornare. Ut commodo ligula vitae malesuada maximus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam sagittis justo non justo placerat, a dapibus sapien volutpat. Nullam ullamcorper sodales justo sed.",
userId: authorId,
})
.onConflictDoNothing()
.returning();

const oneYearFromToday = new Date(now);
oneYearFromToday.setFullYear(oneYearFromToday.getFullYear() + 1);

await Promise.all([
db
.insert(post)
.values({
id: publishedPostId,
published: now,
excerpt: articleExcerpt,
updatedAt: now,
slug: "e2e-test-slug-published",
likes: 10,
readTimeMins: 3,
title: "Published Article",
body: articleContent,
userId: authorId,
})
.onConflictDoNothing()
.returning(),

db
.insert(post)
.values({
id: draftPostId,
published: null,
excerpt: articleExcerpt,
updatedAt: now,
slug: "e2e-test-slug-draft",
likes: 10,
readTimeMins: 3,
title: "Draft Article",
body: articleContent,
userId: authorId,
})
.onConflictDoNothing()
.returning(),

db
.insert(post)
.values({
id: scheduledPostId,
published: oneYearFromToday.toISOString(),
excerpt: articleExcerpt,
updatedAt: now,
slug: "e2e-test-slug-scheduled",
likes: 10,
readTimeMins: 3,
title: "Scheduled Article",
body: articleContent,
userId: authorId,
})
.onConflictDoNothing()
.returning(),
]);

await db
.insert(comment)
.values({
postId,
postId: publishedPostId,
body: "What a great article! Thanks for sharing",
userId: commenterId,
})
Expand Down
4 changes: 4 additions & 0 deletions e2e/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const articleContent =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vitae ipsum id metus vestibulum rutrum eget a diam. Integer eget vulputate risus, ac convallis nulla. Mauris sed augue nunc. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam congue posuere tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut ac augue non libero ullamcorper ornare. Ut commodo ligula vitae malesuada maximus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Etiam sagittis justo non justo placerat, a dapibus sapien volutpat. Nullam ullamcorper sodales justo sed.";

export const articleExcerpt = "Lorem ipsum dolor sit amet";
1 change: 1 addition & 0 deletions e2e/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./utils";
export * from "./constants";
Loading

0 comments on commit 87038df

Please sign in to comment.