@@ -241,8 +316,13 @@ export default function DocsIOS({ locale = "en" }: { locale?: Locale }) {
{isDe ? "Direkter Support" : "Direct support"}
- {isDe ? "Für konkrete Fragen zur iOS-Version: " : "For specific questions about the iOS edition, email "}
-
+ {isDe
+ ? "Für konkrete Fragen zur iOS-Version: "
+ : "For specific questions about the iOS edition, email "}
+
hello@typewhisper.com
.
diff --git a/src/pages/docs/ios/_guide.tsx b/src/pages/docs/ios/_guide.tsx
index 60f8a3f..ef24c35 100644
--- a/src/pages/docs/ios/_guide.tsx
+++ b/src/pages/docs/ios/_guide.tsx
@@ -6,6 +6,7 @@ import {
type IosDocSlug,
} from "@/data/ios-docs";
import { localePath, screenshotPath, type Locale } from "@/i18n/index";
+import { getIosAppStoreUrl, iosVersion } from "@/lib/platform-download";
function imageClass(layout: "phone" | "tablet" | "wide" = "wide") {
if (layout === "phone") {
@@ -26,6 +27,7 @@ export default function DocsIOSGuide({
}) {
const page = getIosDocPage(locale, slug);
const isDe = locale === "de";
+ const iosAppStoreUrl = getIosAppStoreUrl(locale);
return (
@@ -42,7 +44,20 @@ export default function DocsIOSGuide({
diff --git a/tests/download-routing.spec.ts b/tests/download-routing.spec.ts
index 33c3eca..bc9b13a 100644
--- a/tests/download-routing.spec.ts
+++ b/tests/download-routing.spec.ts
@@ -1,6 +1,11 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "node:fs";
+const IOS_APP_STORE_URL_EN =
+ "https://apps.apple.com/us/app/typewhisper-app/id6759319267";
+const IOS_APP_STORE_URL_DE =
+ "https://apps.apple.com/de/app/typewhisper-app/id6759319267";
+
type GeneratedDownloads = {
mac: { url: string };
windows: { url: string };
@@ -52,7 +57,9 @@ const landingScenarios: LandingScenario[] = [
name: "iOS",
userAgent:
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1",
- expectedLabel: "Not public yet",
+ expectedLabel: "Download on the App Store",
+ expectedHref: IOS_APP_STORE_URL_EN,
+ opensNewTab: true,
},
];
@@ -285,8 +292,8 @@ test.describe("release status download routing", () => {
page.getByRole("link", { name: "Download GitHub installer" }),
).toHaveAttribute("href", downloads.windows.url);
await expect(
- page.getByText("Not in the App Store yet", { exact: true }),
- ).toBeVisible();
+ page.getByRole("link", { name: "Download on the App Store" }),
+ ).toHaveAttribute("href", IOS_APP_STORE_URL_EN);
await expect(page.locator('a[href*="testflight.apple.com"]')).toHaveCount(
0,
);
@@ -320,15 +327,15 @@ test.describe("release status download routing", () => {
page.getByRole("link", { name: "GitHub-Installer herunterladen" }),
).toHaveAttribute("href", downloads.windows.url);
await expect(
- page.getByText("Noch nicht im App Store", { exact: true }),
- ).toBeVisible();
+ page.getByRole("link", { name: "Im App Store laden" }),
+ ).toHaveAttribute("href", IOS_APP_STORE_URL_DE);
await expect(page.locator('a[href*="testflight.apple.com"]')).toHaveCount(
0,
);
});
});
-test("public iOS pages show release-preparation status without beta links", async ({
+test("public iOS pages expose the stable App Store release without beta links", async ({
page,
}) => {
for (const path of ["/en/", "/en/docs", "/en/docs/ios", "/en/support"]) {
@@ -338,16 +345,49 @@ test("public iOS pages show release-preparation status without beta links", asyn
);
}
+ await page.goto("/en/");
+ const footerAppStoreLink = page.locator(
+ 'footer a[data-download-target="ios_app_store"]',
+ );
+ await expect(footerAppStoreLink).toHaveAttribute(
+ "data-tracking-placement",
+ "footer",
+ );
+
await page.goto("/en/docs/ios");
await expect(
- page.getByText("App Store release in preparation", { exact: true }),
- ).toBeVisible();
+ page.getByRole("link", { name: "Download on the App Store" }),
+ ).toHaveAttribute("href", IOS_APP_STORE_URL_EN);
await expect(
- page.getByText("Version 1.0 in preparation", { exact: true }).first(),
+ page.getByText("Version 1.0 stable", { exact: true }),
).toBeVisible();
+
+ await page.goto("/en/support");
+ await expect(
+ page.getByRole("link", { name: "Email iOS support" }),
+ ).toHaveAttribute("href", "mailto:hello@typewhisper.com");
+ const supportAppStoreLink = page.getByRole("link", {
+ name: "Open the App Store",
+ });
+ await expect(supportAppStoreLink).toHaveAttribute(
+ "href",
+ IOS_APP_STORE_URL_EN,
+ );
+ await expect(supportAppStoreLink).toHaveAttribute(
+ "data-download-target",
+ "ios_app_store",
+ );
+ await expect(supportAppStoreLink).toHaveAttribute(
+ "data-download-version",
+ "1.0",
+ );
+ await expect(supportAppStoreLink).toHaveAttribute(
+ "data-tracking-placement",
+ "support",
+ );
});
-test.describe("iOS coming-soon media", () => {
+test.describe("iOS App Store media", () => {
test("English landing uses the localized App Preview", async ({
page,
request,
@@ -361,7 +401,18 @@ test.describe("iOS coming-soon media", () => {
await iosTab.click();
await expect(iosTab).toHaveAttribute("aria-selected", "true");
- await expect(page.getByTestId("landing-hero-download")).toBeDisabled();
+ await expect(page.getByTestId("landing-hero-download")).toHaveAttribute(
+ "href",
+ IOS_APP_STORE_URL_EN,
+ );
+ await expect(page.getByTestId("landing-hero-download")).toHaveAttribute(
+ "data-download-target",
+ "ios_app_store",
+ );
+ await expect(page.getByTestId("landing-hero-download")).toHaveAttribute(
+ "data-download-version",
+ "1.0",
+ );
await expect(
page.locator('source[src="/ios-app-preview-de.mp4"]'),
).toHaveCount(0);
@@ -397,6 +448,10 @@ test.describe("iOS coming-soon media", () => {
const iosTab = page.getByTestId("landing-hero-tab-ios");
await iosTab.click();
await expect(iosTab).toHaveAttribute("aria-selected", "true");
+ await expect(page.getByTestId("landing-hero-download")).toHaveAttribute(
+ "href",
+ IOS_APP_STORE_URL_DE,
+ );
const howItWorks = page.getByTestId("how-it-works");
await howItWorks.scrollIntoViewIfNeeded();
@@ -421,7 +476,18 @@ test.describe("iOS coming-soon media", () => {
await expect(
page.getByRole("heading", { level: 1, name: "iOS" }),
).toBeVisible();
- await expect(page.locator('a[href*="apps.apple.com"]')).toHaveCount(0);
+ const appStoreUrl =
+ locale === "de" ? IOS_APP_STORE_URL_DE : IOS_APP_STORE_URL_EN;
+ const appStoreLinks = page.locator(`a[href="${appStoreUrl}"]`);
+ await expect(appStoreLinks.first()).toBeVisible();
+ await expect(appStoreLinks.first()).toHaveAttribute(
+ "data-download-target",
+ "ios_app_store",
+ );
+ await expect(appStoreLinks.first()).toHaveAttribute(
+ "data-download-version",
+ "1.0",
+ );
await expect(page.locator('a[href*="testflight.apple.com"]')).toHaveCount(
0,
);
@@ -436,7 +502,9 @@ test.describe("iOS coming-soon media", () => {
const pngPath = `/screenshots/${locale}/ios/watch/${name}.png`;
const webpPath = `/screenshots/${locale}/ios/watch/${name}.webp`;
await expect(page.locator(`img[src="${pngPath}"]`)).toBeVisible();
- await expect(page.locator(`source[srcset="${webpPath}"]`)).toHaveCount(1);
+ await expect(page.locator(`source[srcset="${webpPath}"]`)).toHaveCount(
+ 1,
+ );
expect((await request.get(pngPath)).ok()).toBeTruthy();
expect((await request.get(webpPath)).ok()).toBeTruthy();
}
diff --git a/tests/ios-docs.spec.ts b/tests/ios-docs.spec.ts
index aca64eb..9f157c6 100644
--- a/tests/ios-docs.spec.ts
+++ b/tests/ios-docs.spec.ts
@@ -1,35 +1,50 @@
import { expect, test } from "@playwright/test";
import { getIosDocTitle, iosDocSlugs } from "../src/data/ios-docs";
-const guidePages = iosDocSlugs.map((slug) => [
- slug,
- getIosDocTitle("en", slug),
- getIosDocTitle("de", slug),
-] as const);
+const guidePages = iosDocSlugs.map(
+ (slug) =>
+ [slug, getIosDocTitle("en", slug), getIosDocTitle("de", slug)] as const,
+);
test.describe("detailed iOS documentation", () => {
- test("overview links every guide and uses current Studio media", async ({ page }) => {
+ test("overview links every guide and uses current Studio media", async ({
+ page,
+ }) => {
await page.goto("/en/docs/ios");
for (const [slug, englishTitle] of guidePages) {
await expect(
- page.locator(`a[href="/en/docs/ios/${slug}"]`).filter({ hasText: englishTitle }).first(),
+ page
+ .locator(`a[href="/en/docs/ios/${slug}"]`)
+ .filter({ hasText: englishTitle })
+ .first(),
).toBeVisible();
}
- await expect(page.locator('source[src="/ios-app-preview-en.mp4"]')).toHaveCount(1);
+ await expect(
+ page.locator('source[src="/ios-app-preview-en.mp4"]'),
+ ).toHaveCount(1);
await expect(
page.locator('img[src="/screenshots/en/ios/ipad/03-inbox.png"]'),
).toBeVisible();
await expect(
page.locator('source[srcset="/screenshots/en/ios/ipad/03-inbox.webp"]'),
).toHaveCount(1);
- await expect(page.locator('a[href*="testflight.apple.com"]')).toHaveCount(0);
- await expect(page.locator('a[href*="apps.apple.com"]')).toHaveCount(0);
+ await expect(page.locator('a[href*="testflight.apple.com"]')).toHaveCount(
+ 0,
+ );
+ await expect(
+ page.getByRole("link", { name: "Download on the App Store" }),
+ ).toHaveAttribute(
+ "href",
+ "https://apps.apple.com/us/app/typewhisper-app/id6759319267",
+ );
});
for (const locale of ["en", "de"] as const) {
- test(`${locale} guide routes render with the iOS sidebar`, async ({ page }) => {
+ test(`${locale} guide routes render with the iOS sidebar`, async ({
+ page,
+ }) => {
for (const [slug, englishTitle, germanTitle] of guidePages) {
await page.goto(`/${locale}/docs/ios/${slug}`);
await expect(
@@ -41,38 +56,102 @@ test.describe("detailed iOS documentation", () => {
await expect(
page.locator(`nav a[href="/${locale}/docs/ios/${slug}"]`),
).toHaveClass(/border-primary/);
+ await expect(
+ page.getByText(locale === "de" ? "Stabil" : "Stable", {
+ exact: true,
+ }).first(),
+ ).toBeVisible();
}
});
}
- test("keyboard guide explains Full Access and the iOS return flow", async ({ page }) => {
+ test("keyboard guide explains Full Access and the iOS return flow", async ({
+ page,
+ }) => {
await page.goto("/en/docs/ios/dictation-and-keyboard");
- await expect(page.getByText("Settings > General > Keyboard > Keyboards > Add New Keyboard")).toBeVisible();
- await expect(page.getByText(/Normal typing works without it/)).toBeVisible();
- await expect(page.getByText(/iOS keyboard extensions cannot capture microphone audio directly/)).toBeVisible();
+ await expect(
+ page.getByText(
+ "Settings > General > Keyboard > Keyboards > Add New Keyboard",
+ ),
+ ).toBeVisible();
+ await expect(
+ page.getByText(/Normal typing works without it/),
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ /iOS keyboard extensions cannot capture microphone audio directly/,
+ ),
+ ).toBeVisible();
await expect(page.getByText(/previous-app link/).first()).toBeVisible();
});
- test("Inbox, Shortcuts, privacy, and Premium behavior are documented", async ({ page }) => {
+ test("installation and troubleshooting describe the public App Store path", async ({
+ page,
+ }) => {
+ await page.goto("/en/docs/ios/installation");
+ await expect(page.getByText(/available from the App Store/)).toBeVisible();
+ await expect(
+ page.getByRole("link", { name: "Open the App Store" }),
+ ).toHaveAttribute(
+ "href",
+ "https://apps.apple.com/us/app/typewhisper-app/id6759319267",
+ );
+
+ await page.goto("/en/docs/ios/troubleshooting");
+ await expect(
+ page.getByRole("heading", {
+ name: "The App Store download does not work",
+ }),
+ ).toBeVisible();
+ await expect(
+ page.getByText(/official App Store product page/),
+ ).toBeVisible();
+ });
+
+ test("Inbox, Shortcuts, privacy, and Premium behavior are documented", async ({
+ page,
+ }) => {
await page.goto("/en/docs/ios/files-history-and-inbox");
- await expect(page.getByText(/Quick Dictations, Apple Watch recordings, calendar drafts/)).toBeVisible();
- await expect(page.locator('img[src="/screenshots/en/ios/ipad/03-inbox.png"]')).toBeVisible();
+ await expect(
+ page.getByText(
+ /Quick Dictations, Apple Watch recordings, calendar drafts/,
+ ),
+ ).toBeVisible();
+ await expect(
+ page.locator('img[src="/screenshots/en/ios/ipad/03-inbox.png"]'),
+ ).toBeVisible();
await page.goto("/en/docs/ios/watch-and-shortcuts");
- await expect(page.getByText(/Get Last Transcription returns/)).toBeVisible();
- await expect(page.getByText(/assign that Shortcut to the Action Button/)).toBeVisible();
+ await expect(
+ page.getByText(/Get Last Transcription returns/),
+ ).toBeVisible();
+ await expect(
+ page.getByText(/assign that Shortcut to the Action Button/),
+ ).toBeVisible();
await page.goto("/en/docs/ios/privacy-and-premium");
- await expect(page.getByText(/Normal keyboard typing works without Full Access/)).toBeVisible();
- await expect(page.getByText(/History and Inbox text and metadata/)).toBeVisible();
- await expect(page.getByText(/does not pass through the TypeWhisper entitlement service/)).toBeVisible();
+ await expect(
+ page.getByText(/Normal keyboard typing works without Full Access/),
+ ).toBeVisible();
+ await expect(
+ page.getByText(/History and Inbox text and metadata/),
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ /does not pass through the TypeWhisper entitlement service/,
+ ),
+ ).toBeVisible();
});
- test("snippet placeholders expose localized list semantics", async ({ page }) => {
+ test("snippet placeholders expose localized list semantics", async ({
+ page,
+ }) => {
await page.goto("/en/docs/ios/dictionary-and-snippets");
- const placeholders = page.getByRole("list", { name: "Snippet placeholders" });
+ const placeholders = page.getByRole("list", {
+ name: "Snippet placeholders",
+ });
await expect(placeholders).toBeVisible();
await expect(placeholders.getByRole("listitem")).toHaveCount(5);
});
diff --git a/tests/ux-quick-wins.spec.ts b/tests/ux-quick-wins.spec.ts
index 2172f85..0250a89 100644
--- a/tests/ux-quick-wins.spec.ts
+++ b/tests/ux-quick-wins.spec.ts
@@ -60,6 +60,39 @@ test.describe("docs platform logos", () => {
await expect(page.locator('[data-platform-logo="macos"]').first()).toBeVisible();
await expect(page.locator('[data-platform-logo="ios"]').first()).toBeVisible();
});
+
+ for (const locale of ["en", "de"] as const) {
+ test(`${locale} docs release badges include platform versions`, async ({
+ page,
+ }) => {
+ const windowsBadge = locale === "de" ? "1.0 Stabil" : "1.0 Stable";
+
+ await page.goto(`/${locale}/docs`);
+ await expect(page.locator("main")).toContainText("Windows 1.0.9");
+ await expect(
+ page.locator(`a[href="/${locale}/docs/mac"]`).getByText("1.6", {
+ exact: true,
+ }),
+ ).toBeVisible();
+ await expect(
+ page
+ .locator(`a[href="/${locale}/docs/windows"]`)
+ .getByText(windowsBadge, { exact: true }),
+ ).toBeVisible();
+ await expect(
+ page
+ .locator(`a[href="/${locale}/docs/ios"]`)
+ .getByText(locale === "de" ? "1.0 Stabil" : "1.0 Stable", {
+ exact: true,
+ }),
+ ).toBeVisible();
+
+ await page.goto(`/${locale}/docs/windows`);
+ await expect(
+ page.getByText(windowsBadge, { exact: true }).last(),
+ ).toBeVisible();
+ });
+ }
});
test.describe("brand logos", () => {