Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
test/e2e/__screenshots__/** filter=lfs diff=lfs merge=lfs -text
.github/* linguist-vendored=false
.astro/** linguist-vendored
3 changes: 2 additions & 1 deletion .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: CodeQL config

# Unavoidable checkoutin untrusted environment to use
# Unavoidable checkout in untrusted environment to use
# "astro db verify" and "astro db push --remote" commands
# @see PR https://github.com/withastro/astro/pull/15069
paths-ignore:
- .github/workflows/deployment-preview.yml
- .github/workflows/deployment-production.yml
4 changes: 0 additions & 4 deletions _TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,6 @@ Google Calendar, Apple Calendar, Yahoo Calender, Microsoft 365, Outlook, and T

[Relative Time](https://github.com/BryceRussell/astro-github-elements/tree/main/packages/time#readme)

### Display text in a circular layout

[TextCircle](https://github.com/LoStisWorld/astro-textcircle#astro-textcircle)

## Markdown

## Code Block and Highlighting
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@astrojs/sitemap": "^3.6.0",
"@astrojs/vercel": "^9.0.2",
"@axe-core/playwright": "^4.11.0",
"@github/relative-time-element": "^5.0.0",
"@glidejs/glide": "^3.7.1",
"@googlemaps/extended-component-library": "^0.6.14",
"@nanostores/lit": "^0.2.3",
Expand Down
127 changes: 127 additions & 0 deletions src/components/Time/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// @vitest-environment node

import { beforeEach, describe, expect, test, vi } from 'vitest'
import { experimental_AstroContainer as AstroContainer } from 'astro/container'
import { withJsdomEnvironment } from '@test/unit/helpers/litRuntime'

describe('Time (Astro)', () => {
let container: AstroContainer

beforeEach(async () => {
container = await AstroContainer.create()
})

test('renders a <relative-time> element and keeps slot fallback content', async () => {
const Time = (await import('@components/Time/index.astro')).default

const renderedHtml = await container.renderToString(Time, {
props: {
format: 'relative',
datetime: '2020-01-01T00:00:00Z',
},
slots: {
default: 'Fallback text',
},
})

await withJsdomEnvironment(async ({ window }) => {
window.document.body.innerHTML = renderedHtml

const element = window.document.querySelector('relative-time')
expect(element).toBeTruthy()
expect(element?.getAttribute('datetime')).toBe('2020-01-01T00:00:00Z')
expect(element?.textContent).toContain('Fallback text')
})
})

test('serializes Date datetime to ISO string', async () => {
const Time = (await import('@components/Time/index.astro')).default
const date = new Date('2024-02-29T12:34:56.000Z')

const renderedHtml = await container.renderToString(Time, {
props: {
format: 'datetime',
datetime: date,
},
})

await withJsdomEnvironment(async ({ window }) => {
window.document.body.innerHTML = renderedHtml

const element = window.document.querySelector('relative-time')
expect(element?.getAttribute('datetime')).toBe('2024-02-29T12:34:56.000Z')
})
})

test('uses current time when now=true and datetime is omitted', async () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2030-01-02T03:04:05.000Z'))

try {
const Time = (await import('@components/Time/index.astro')).default

const renderedHtml = await container.renderToString(Time, {
props: {
format: 'relative',
now: true,
},
})

await withJsdomEnvironment(async ({ window }) => {
window.document.body.innerHTML = renderedHtml

const element = window.document.querySelector('relative-time')
expect(element?.getAttribute('datetime')).toBe('2030-01-02T03:04:05.000Z')
})
} finally {
vi.useRealTimers()
}
})

test('maps formatStyle and timeZoneName to kebab-case attributes', async () => {
const Time = (await import('@components/Time/index.astro')).default

const renderedHtml = await container.renderToString(Time, {
props: {
format: 'datetime',
datetime: '2020-01-01T00:00:00Z',
formatStyle: 'short',
timeZoneName: 'shortOffset',
},
})

await withJsdomEnvironment(async ({ window }) => {
window.document.body.innerHTML = renderedHtml

const element = window.document.querySelector('relative-time')
expect(element?.getAttribute('format-style')).toBe('short')
expect(element?.getAttribute('time-zone-name')).toBe('shortOffset')
})
})

test('passes through supported attributes to <relative-time>', async () => {
const Time = (await import('@components/Time/index.astro')).default

const renderedHtml = await container.renderToString(Time, {
props: {
format: 'relative',
datetime: '2020-01-01T00:00:00Z',
tense: 'past',
precision: 'day',
threshold: 'P1D',
prefix: 'about',
},
})

await withJsdomEnvironment(async ({ window }) => {
window.document.body.innerHTML = renderedHtml

const element = window.document.querySelector('relative-time')
expect(element?.getAttribute('format')).toBe('relative')
expect(element?.getAttribute('tense')).toBe('past')
expect(element?.getAttribute('precision')).toBe('day')
expect(element?.getAttribute('threshold')).toBe('P1D')
expect(element?.getAttribute('prefix')).toBe('about')
})
})
})
41 changes: 41 additions & 0 deletions src/components/Time/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
type Common = {
format: 'datetime'|'relative'|'duration';
tense?: 'auto'|'past'|'future';
precision?: 'year'|'month'|'day'|'hour'|'minute'|'second';
threshold?: string;
prefix?: string;
formatStyle?: 'long'|'short'|'narrow';
second?: 'numeric'|'2-digit';
minute?: 'numeric'|'2-digit';
hour?: 'numeric'|'2-digit';
weekday?: 'short'|'long'|'narrow';
day?: 'numeric'|'2-digit';
month?: 'numeric'|'2-digit'|'short'|'long'|'narrow';
year?: 'numeric'|'2-digit';
timeZoneName?: 'long'|'short'|'shortOffset'|'longOffset' |'shortGeneric'|'longGeneric';
}

export interface Props extends Common {
datetime?: Date | string
now?: boolean
}

const { now, datetime, formatStyle, timeZoneName, ...attrs } = Astro.props as Props
const resolvedDatetime = datetime ?? (now ? new Date() : undefined)
---

<script>
import '@github/relative-time-element'
</script>

<relative-time
datetime={resolvedDatetime instanceof Date ? resolvedDatetime.toISOString() : resolvedDatetime}
{...{
...attrs,
'format-style':formatStyle,
'time-zone-name':timeZoneName,
}}
>
<slot />
</relative-time>
8 changes: 8 additions & 0 deletions src/content/articles/demo/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ Display customer feedback and testimonials:

<Testimonials count={3} />

### Time Component

```markdown
Let's go to the disco <Time datetime="2026-01-01T00:00:00.000Z" />.
```

Let's go to the disco <Time datetime="2026-01-01T00:00:00.000Z" />.

## GFM

### Footnote
Expand Down
3 changes: 2 additions & 1 deletion src/layouts/MarkdownLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import Icon from '@components/Icon/index.astro'
import MastodonModal from '@components/Social/Mastodon/index.astro'
import Newsletter from '@components/CallToAction/Newsletter/index.astro'
import Shares from '@components/Social/Shares/index.astro'
import Time from '@components/Time/index.astro'
import Testimonials from '@components/Testimonials/index.astro'

/** Export components for use in MDX */
const Components = { Avatar, Callout, Carousel, Contact, Embed, Featured, Highlighter, Icon, Image, MastodonModal, Newsletter, Picture, Shares, Testimonials }
const Components = { Avatar, Callout, Carousel, Contact, Embed, Featured, Highlighter, Icon, Image, MastodonModal, Newsletter, Picture, Shares, Testimonials, Time }

export interface Props {
/** Author name */
Expand Down