feat: add /pricing and /changelog SEO pages
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 10m43s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 10m43s
- Pricing page with full comparison table, feature matrix, FAQ, JSON-LD Product schema - Changelog page with all versions v0.1.0-v0.6.0, JSON-LD Blog schema - 301 redirects for clean URLs - Added to sitemap.xml - Pricing in main nav, changelog in footer - 14 new tests (171 total)
This commit is contained in:
parent
9d1170fb9a
commit
9609501d7b
6 changed files with 652 additions and 1 deletions
|
|
@ -138,6 +138,8 @@ for (const page of ["social-media-previews", "website-monitoring", "pdf-reports"
|
|||
// Clean URLs for SEO pages
|
||||
app.get("/compare", (_req, res) => res.redirect(301, "/compare.html"));
|
||||
app.get("/guides/quick-start", (_req, res) => res.redirect(301, "/guides/quick-start.html"));
|
||||
app.get("/pricing", (_req, res) => res.redirect(301, "/pricing.html"));
|
||||
app.get("/changelog", (_req, res) => res.redirect(301, "/changelog.html"));
|
||||
|
||||
// Static files (landing page)
|
||||
app.use(express.static(path.join(__dirname, "../public"), { etag: true }));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ function createApp() {
|
|||
// Clean URL redirects matching index.ts
|
||||
app.get('/compare', (_req, res) => res.redirect(301, '/compare.html'))
|
||||
app.get('/guides/quick-start', (_req, res) => res.redirect(301, '/guides/quick-start.html'))
|
||||
app.get('/pricing', (_req, res) => res.redirect(301, '/pricing.html'))
|
||||
app.get('/changelog', (_req, res) => res.redirect(301, '/changelog.html'))
|
||||
|
||||
app.use(express.static(publicDir, { etag: true }))
|
||||
return app
|
||||
|
|
@ -92,16 +94,146 @@ describe('Quick-Start Guide', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('Pricing Page', () => {
|
||||
const app = createApp()
|
||||
|
||||
it('GET /pricing.html returns 200', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toContain('text/html')
|
||||
})
|
||||
|
||||
it('GET /pricing redirects 301 to .html', async () => {
|
||||
const res = await request(app).get('/pricing')
|
||||
expect(res.status).toBe(301)
|
||||
expect(res.headers.location).toBe('/pricing.html')
|
||||
})
|
||||
|
||||
it('contains required SEO elements', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
const html = res.text
|
||||
expect(html).toMatch(/<title>.+<\/title>/)
|
||||
expect(html).toMatch(/<meta name="description" content=".+"/)
|
||||
expect(html).toMatch(/<meta property="og:title"/)
|
||||
expect(html).toMatch(/<meta property="og:description"/)
|
||||
expect(html).toMatch(/<meta property="og:type" content="website"/)
|
||||
expect(html).toMatch(/<link rel="canonical" href="https:\/\/snapapi\.eu\/pricing"/)
|
||||
expect(html).toMatch(/<meta name="twitter:card"/)
|
||||
})
|
||||
|
||||
it('contains JSON-LD Product schema with offers', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
const html = res.text
|
||||
expect(html).toContain('"@type":"Product"')
|
||||
expect(html).toContain('"Offer"')
|
||||
expect(html).toContain('"9.00"')
|
||||
expect(html).toContain('"29.00"')
|
||||
expect(html).toContain('"79.00"')
|
||||
})
|
||||
|
||||
it('contains all three pricing tiers', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
const html = res.text
|
||||
expect(html).toContain('Starter')
|
||||
expect(html).toContain('Pro')
|
||||
expect(html).toContain('Business')
|
||||
expect(html).toContain('€9')
|
||||
expect(html).toContain('€29')
|
||||
expect(html).toContain('€79')
|
||||
})
|
||||
|
||||
it('contains feature comparison matrix', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
const html = res.text
|
||||
expect(html).toContain('PNG')
|
||||
expect(html).toContain('Full-page')
|
||||
expect(html).toContain('Custom viewport')
|
||||
})
|
||||
|
||||
it('contains FAQ section', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
const html = res.text
|
||||
expect(html).toContain('FAQ')
|
||||
expect(html).toContain('billing')
|
||||
})
|
||||
|
||||
it('contains checkout CTA buttons', async () => {
|
||||
const res = await request(app).get('/pricing.html')
|
||||
const html = res.text
|
||||
expect(html).toContain("checkout('starter')")
|
||||
expect(html).toContain("checkout('pro')")
|
||||
expect(html).toContain("checkout('business')")
|
||||
})
|
||||
})
|
||||
|
||||
describe('Changelog Page', () => {
|
||||
const app = createApp()
|
||||
|
||||
it('GET /changelog.html returns 200', async () => {
|
||||
const res = await request(app).get('/changelog.html')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers['content-type']).toContain('text/html')
|
||||
})
|
||||
|
||||
it('GET /changelog redirects 301 to .html', async () => {
|
||||
const res = await request(app).get('/changelog')
|
||||
expect(res.status).toBe(301)
|
||||
expect(res.headers.location).toBe('/changelog.html')
|
||||
})
|
||||
|
||||
it('contains required SEO elements', async () => {
|
||||
const res = await request(app).get('/changelog.html')
|
||||
const html = res.text
|
||||
expect(html).toMatch(/<title>.+<\/title>/)
|
||||
expect(html).toMatch(/<meta name="description" content=".+"/)
|
||||
expect(html).toMatch(/<meta property="og:title"/)
|
||||
expect(html).toMatch(/<meta property="og:description"/)
|
||||
expect(html).toMatch(/<link rel="canonical" href="https:\/\/snapapi\.eu\/changelog"/)
|
||||
expect(html).toMatch(/<meta name="twitter:card"/)
|
||||
})
|
||||
|
||||
it('contains JSON-LD Blog schema', async () => {
|
||||
const res = await request(app).get('/changelog.html')
|
||||
const html = res.text
|
||||
expect(html).toContain('"@type":"Blog"')
|
||||
})
|
||||
|
||||
it('contains all version entries in order', async () => {
|
||||
const res = await request(app).get('/changelog.html')
|
||||
const html = res.text
|
||||
const v06 = html.indexOf('v0.6.0')
|
||||
const v05 = html.indexOf('v0.5.0')
|
||||
const v04 = html.indexOf('v0.4.0')
|
||||
const v03 = html.indexOf('v0.3.0')
|
||||
const v02 = html.indexOf('v0.2.0')
|
||||
const v01 = html.indexOf('v0.1.0')
|
||||
expect(v06).toBeGreaterThan(-1)
|
||||
expect(v05).toBeGreaterThan(v06)
|
||||
expect(v04).toBeGreaterThan(v05)
|
||||
expect(v03).toBeGreaterThan(v04)
|
||||
expect(v02).toBeGreaterThan(v03)
|
||||
expect(v01).toBeGreaterThan(v02)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sitemap & Index updates', () => {
|
||||
it('sitemap contains new URLs', () => {
|
||||
const sitemap = fs.readFileSync(path.join(publicDir, 'sitemap.xml'), 'utf-8')
|
||||
expect(sitemap).toContain('https://snapapi.eu/compare')
|
||||
expect(sitemap).toContain('https://snapapi.eu/guides/quick-start')
|
||||
expect(sitemap).toContain('https://snapapi.eu/pricing')
|
||||
expect(sitemap).toContain('https://snapapi.eu/changelog')
|
||||
})
|
||||
|
||||
it('index.html has links to new pages', () => {
|
||||
const index = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf-8')
|
||||
expect(index).toContain('/compare')
|
||||
expect(index).toContain('/guides/quick-start')
|
||||
expect(index).toContain('/pricing')
|
||||
})
|
||||
|
||||
it('footer has changelog link', () => {
|
||||
const index = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf-8')
|
||||
expect(index).toContain('/changelog')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue