Skip to content

Commit

Permalink
feat: upgrade to support lens v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Kris Urbas committed Nov 2, 2023
1 parent 49e3a5f commit ea9f1a4
Show file tree
Hide file tree
Showing 15 changed files with 2,544 additions and 1,147 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ engine-strict=true
auto-install-peers=false
strict-peer-dependencies=false
link-workspace-packages=false
node-linker=hoisted # required to fix zod version incompatibility
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ https://share.lens.xyz/u/<handle>[?by=<appId>]

Where:

- `<handle>` is the profile handle inclusive of the `.lens` suffix but without the `@` prefix (e.g. `alice.lens`, `bob.lens`, etc.).
- `<handle>` is the profile v2 full handle including the namespace (e.g. `lens/alice`, `lens/bob`, etc.).
- `<handle>` is the profile v1 handle inclusive of the `.lens` suffix but without the `@` prefix (e.g. `alice.lens`, `bob.lens`, etc.).
- `<appId>` is an optional parameter that reflect the Lens App ID of the app used to generate the Lens Share Link. This is used to give priority to the app that generated the Lens Share Link when the user opens the Lens Share Link.

Some examples:
Expand Down
18 changes: 14 additions & 4 deletions e2e/fixtures/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ import { test as base, expect } from "@playwright/test";
import { ProfilePage } from "./ProfilePage";

export const test = base.extend<{
anyProfile: ProfilePage;
v1Profile: ProfilePage;
v1ProfileWithSuffix: ProfilePage;
v2Profile: ProfilePage;
}>({
anyProfile: async ({ page }, use) => {
const publication = new ProfilePage(page, "lensprotocol");
await use(publication);
v1Profile: async ({ page }, use) => {
const profile = new ProfilePage(page, "lensprotocol");
await use(profile);
},
v1ProfileWithSuffix: async ({ page }, use) => {
const profile = new ProfilePage(page, "lensprotocol.lens");
await use(profile);
},
v2Profile: async ({ page }, use) => {
const profile = new ProfilePage(page, "lens/lensprotocol");
await use(profile);
},
});

Expand Down
140 changes: 102 additions & 38 deletions e2e/profiles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,137 @@ test.use(devices["Desktop Chrome"]);

test.describe("Given a Profile link", async () => {
test.describe("When opening it", async () => {
test("Then it should show relevant app options", async ({ anyProfile }) => {
await anyProfile.open();
test("Then it should show relevant app options", async ({ v1Profile }) => {
await v1Profile.open();

await expect(anyProfile.options).toHaveText([
await expect(v1Profile.options).toHaveText([
"Buttrfly",
"Collectz",
"Hey",
"LensFrens",
"Lensta",
"Riff",
"Soclly",
"Tape"
"Tape",
]);
});
});
});

test.describe("Given a Publication link posted on a social media website/app", async () => {
test.describe("Given a v1 Profile link posted on a social media website/app", async () => {
test.describe("When checking Open Graph meta tags", async () => {
test("Then it should render the expected base-line meta tags", async ({ v1Profile }) => {
await v1Profile.open();

expect(await v1Profile.extractOpenGraphProperties()).toMatchObject({
"og:title": `${v1Profile.handle} profile`,
"og:description": "The Social Layer for Web3 🌿",
"og:url": expect.stringContaining(`/u/${v1Profile.handle}`),
"og:site_name": "Lens Share",
"og:type": "profile",
});
});

test("Then it should include the expected Twitter Card meta tags", async ({ v1Profile }) => {
await v1Profile.open();

expect(await v1Profile.extractTwitterMetaTags()).toEqual({
"twitter:card": "summary_large_image",
"twitter:site": "LensProtocol",
"twitter:title": `${v1Profile.handle} profile`,
"twitter:description": "The Social Layer for Web3 🌿",
"twitter:image": expect.any(String),
"twitter:image:type": "image/png",
});
});
});
});

test.describe("Given a v1 Profile link with suffix posted on a social media website/app", async () => {
test.describe("When checking Open Graph meta tags", async () => {
test("Then it should render the expected base-line meta tags", async ({
v1ProfileWithSuffix,
}) => {
await v1ProfileWithSuffix.open();

expect(await v1ProfileWithSuffix.extractOpenGraphProperties()).toMatchObject({
"og:title": `${v1ProfileWithSuffix.handle} profile`,
"og:description": "The Social Layer for Web3 🌿",
"og:url": expect.stringContaining(`/u/${v1ProfileWithSuffix.handle}`),
"og:site_name": "Lens Share",
"og:type": "profile",
});
});

test("Then it should include the expected Twitter Card meta tags", async ({
v1ProfileWithSuffix,
}) => {
await v1ProfileWithSuffix.open();

expect(await v1ProfileWithSuffix.extractTwitterMetaTags()).toEqual({
"twitter:card": "summary_large_image",
"twitter:site": "LensProtocol",
"twitter:title": `${v1ProfileWithSuffix.handle} profile`,
"twitter:description": "The Social Layer for Web3 🌿",
"twitter:image": expect.any(String),
"twitter:image:type": "image/png",
});
});
});
});

test.describe("Given a v2 Profile link posted on a social media website/app", async () => {
test.describe("When checking Open Graph meta tags", async () => {
test("Then it should render the expected base-line meta tags", async ({ anyProfile }) => {
await anyProfile.open();
test("Then it should render the expected base-line meta tags", async ({ v2Profile }) => {
await v2Profile.open();

expect(await anyProfile.extractOpenGraphProperties()).toMatchObject({
"og:title": `@${anyProfile.handle} profile`,
expect(await v2Profile.extractOpenGraphProperties()).toMatchObject({
"og:title": `${v2Profile.handle} profile`,
"og:description": "The Social Layer for Web3 🌿",
"og:url": expect.stringContaining(`/u/${anyProfile.handle}`),
"og:url": expect.stringContaining(`/u/${v2Profile.handle}`),
"og:site_name": "Lens Share",
"og:type": "profile",
});
});

test("Then it should include the expected Twitter Card meta tags", async ({ anyProfile }) => {
await anyProfile.open();
test("Then it should include the expected Twitter Card meta tags", async ({ v2Profile }) => {
await v2Profile.open();

expect(await anyProfile.extractTwitterMetaTags()).toEqual({
expect(await v2Profile.extractTwitterMetaTags()).toEqual({
"twitter:card": "summary_large_image",
"twitter:site": "LensProtocol",
"twitter:title": `@${anyProfile.handle} profile`,
"twitter:title": `${v2Profile.handle} profile`,
"twitter:description": "The Social Layer for Web3 🌿",
"twitter:image": expect.any(String),
"twitter:image:type": "image/png",
});
});
});
});

test.describe("Given a Profile link posted on a social media website/app", async () => {
test.describe("When the link includes the `by` attribution param", async () => {
test("Then it should mention the originating app in page `title` and Open Graph `site_name` tag", async ({
anyProfile,
v1Profile,
}) => {
await anyProfile.openAsSharedBy("Hey");
await v1Profile.openAsSharedBy("Hey");

expect(await anyProfile.getTitle()).toContain("Hey");
expect(await anyProfile.extractOpenGraphProperties()).toMatchObject({
expect(await v1Profile.getTitle()).toContain("Hey");
expect(await v1Profile.extractOpenGraphProperties()).toMatchObject({
"og:site_name": "Hey",
});
});

test("Then it should mention the originating app in Twitter Card `site` if a Twitter handle is provided in the app manifest", async ({
anyProfile,
v1Profile,
}) => {
await anyProfile.openAsSharedBy("Hey");
await v1Profile.openAsSharedBy("Hey");

expect(await anyProfile.getTitle()).toContain("Hey");
expect(await anyProfile.extractOpenGraphProperties()).toMatchObject({
expect(await v1Profile.getTitle()).toContain("Hey");
expect(await v1Profile.extractOpenGraphProperties()).toMatchObject({
"og:site_name": "Hey",
});
expect(await anyProfile.extractTwitterMetaTags()).toMatchObject({
expect(await v1Profile.extractTwitterMetaTags()).toMatchObject({
"twitter:site": "heydotxyz",
});
});
Expand All @@ -81,10 +145,10 @@ test.describe("Given a Publication link posted on a social media website/app", a

test.describe("Given a Profile link with `by` attribution param", async () => {
test.describe("When opening it", async () => {
test("Then it should show the specified app first", async ({ anyProfile }) => {
await anyProfile.openAsSharedBy("Hey");
test("Then it should show the specified app first", async ({ v1Profile }) => {
await v1Profile.openAsSharedBy("Hey");

await expect(anyProfile.options).toHaveText([
await expect(v1Profile.options).toHaveText([
"Hey",
"Buttrfly",
"Collectz",
Expand All @@ -99,33 +163,33 @@ test.describe("Given a Profile link with `by` attribution param", async () => {

test.describe("When opening it on a platform not supported by the specified app", async () => {
test("Then it should show a message an attribution message before offering other options", async ({
anyProfile,
v1Profile,
}) => {
await anyProfile.openAsSharedBy("orb");
await v1Profile.openAsSharedBy("orb");

await expect(anyProfile.context).toHaveText("Shared via Orb mobile app.");
await expect(v1Profile.context).toHaveText("Shared via Orb mobile app.");
});
});
});

test.describe("Given an opened Profile link", async () => {
test.describe("When submitting an app choice", async () => {
test("Then it should open the publication with the selected app", async ({ anyProfile }) => {
await anyProfile.open();
const url = await anyProfile.justOnce("Hey");
test("Then it should open the publication with the selected app", async ({ v1Profile }) => {
await v1Profile.open();
const url = await v1Profile.justOnce("Hey");

await expect(url).toMatch(`https://hey.xyz/u/${anyProfile.handle}`);
expect(url).toMatch(`https://hey.xyz/u/${v1Profile.handle}`);
});
});

test.describe("When submitting an app choice with 'Remember' checkbox selected", async () => {
test("Then it should use the same app for all future publications", async ({ anyProfile }) => {
await anyProfile.open();
await anyProfile.remember("Hey");
test("Then it should use the same app for all future publications", async ({ v1Profile }) => {
await v1Profile.open();
await v1Profile.remember("Hey");

const response = await anyProfile.open();
const response = await v1Profile.open();

await expect(response?.url()).toMatch(`https://hey.xyz/u/${anyProfile.handle}`);
expect(response?.url()).toMatch(`https://hey.xyz/u/${v1Profile.handle}`);
});
});
});
27 changes: 7 additions & 20 deletions e2e/publications.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test.describe("Given a Publication link posted on a social media website/app", a
await textPost.open();

expect(await textPost.extractOpenGraphProperties()).toEqual({
"og:title": "Post by @stani.lens",
"og:title": "Post by lens/stani",
"og:description": "This post will age well.",
"og:url": expect.stringContaining(`/p/${textPost.publicationId}`),
"og:site_name": "Lens Share",
Expand All @@ -42,7 +42,7 @@ test.describe("Given a Publication link posted on a social media website/app", a
expect(await textPost.extractTwitterMetaTags()).toEqual({
"twitter:card": "summary",
"twitter:site": "LensProtocol",
"twitter:title": "Post by @stani.lens",
"twitter:title": "Post by lens/stani",
"twitter:description": "This post will age well.",
});
});
Expand All @@ -53,8 +53,7 @@ test.describe("Given a Publication link posted on a social media website/app", a
await imagePost.open();

expect(await imagePost.extractOpenGraphProperties()).toMatchObject({
"og:image":
"https://ipfs-2.thirdwebcdn.com/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF",
"og:image": "https://gw.ipfs-lens.dev/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF",
"og:image:type": "image/jpeg",
});
});
Expand All @@ -67,7 +66,7 @@ test.describe("Given a Publication link posted on a social media website/app", a
expect(await imagePost.extractTwitterMetaTags()).toMatchObject({
"twitter:card": "summary_large_image",
"twitter:image":
"https://ipfs-2.thirdwebcdn.com/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF",
"https://gw.ipfs-lens.dev/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF",
});
});
});
Expand All @@ -78,7 +77,7 @@ test.describe("Given a Publication link posted on a social media website/app", a

expect(await videoPost.extractOpenGraphProperties()).toMatchObject({
"og:image":
"https://ipfs-2.thirdwebcdn.com/ipfs/bafybeib7o45x6oesq4ziwdatncakvvwqkp6wvur5b45od4fm6gbcbjmce4",
"https://gw.ipfs-lens.dev/ipfs/bafybeib7o45x6oesq4ziwdatncakvvwqkp6wvur5b45od4fm6gbcbjmce4",
});
});
});
Expand Down Expand Up @@ -118,13 +117,7 @@ test.describe("Given a Video Publication link", async () => {
}) => {
await videoPost.open();

await expect(videoPost.options).toHaveText([
"Buttrfly",
"Collectz",
"Hey",
"Soclly",
"Tape",
]);
await expect(videoPost.options).toHaveText(["Buttrfly", "Collectz", "Hey", "Soclly", "Tape"]);
});
});
});
Expand All @@ -134,13 +127,7 @@ test.describe("Given a Publication link with `by` attribution param", async () =
test("Then it should show the specified app first", async ({ videoPost }) => {
await videoPost.openAsSharedBy("tape");

await expect(videoPost.options).toHaveText([
"Tape",
"Buttrfly",
"Collectz",
"Hey",
"Soclly",
]);
await expect(videoPost.options).toHaveText(["Tape", "Buttrfly", "Collectz", "Hey", "Soclly"]);
});
});

Expand Down
27 changes: 14 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "BASE_URL=http://localhost:3000 next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint": "next lint && pnpm run tsc",
"test:dev": "playwright test --ui",
"test": "playwright test"
"test": "playwright test",
"tsc": "tsc --noEmit"
},
"dependencies": {
"@heroicons/react": "^2.0.18",
"@lens-protocol/client": "1.2.0-next.1",
"@lens-protocol/shared-kernel": "^0.8.0",
"@thirdweb-dev/storage": "^1.1.4",
"@lens-protocol/client": "2.0.0-alpha.19",
"@lens-protocol/shared-kernel": "0.11.0-alpha.6",
"@thirdweb-dev/storage": "^1.2.11",
"autoprefixer": "10.4.14",
"clsx": "^1.2.1",
"eslint-config-next": "13.4.5",
Expand All @@ -26,24 +27,24 @@
"react-dom": "18.2.0",
"tailwindcss": "3.3.2",
"typescript": "5.1.3",
"zod": "^3.21.4",
"zod-to-json-schema": "^3.21.1"
"zod": "3.21.4",
"zod-to-json-schema": "3.21.4"
},
"license": "MIT",
"devDependencies": {
"@playwright/test": "^1.35.0",
"@testing-library/jest-dom": "^5.16.5",
"@playwright/test": "^1.39.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@types/node": "20.3.0",
"@types/node": "^18.18.8",
"@types/react": "18.2.11",
"@types/react-dom": "18.2.4",
"dotenv": "^16.3.1",
"eslint": "8.42.0",
"eslint-plugin-import": "^2.27.5"
"eslint-plugin-import": "^2.29.0"
},
"engines": {
"node": "^18.15.0",
"pnpm": "^8.0.0"
},
"packageManager": "pnpm@8.2.0"
"packageManager": "pnpm@8.9.2"
}
Loading

0 comments on commit ea9f1a4

Please sign in to comment.