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
9 changes: 7 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ jobs:
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 24
cache: npm

- name: Install
run: yarn

- name: Run unit tests
run: |
# Run tests once in CI; fail the job if tests fail
yarn test --run

- name: Build
run: yarn build

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
node_modules
dist
stats.html
coverage
.vscode
.DS_Store
playwright-report
test-results
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - yyyy-mm-dd

## [0.4.1] - 2025-12-29

- Some bug fixes

## [0.4.0] - 2025-12-17

- Calf has intelligence now! I'm not sure if the intelligence of a calf, is artificial or not, so I'm not sure about using AI here, or CI. Anyway, you can simply use your favorite AI tool to easily generate a calendar event described as a text in normal human language. For example you can copy/paste a text from your invitation email, or a tweet or where ever you want and Calf will fill the form and create the event for you.
Expand Down
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,33 @@ check:
@yarn audit || true
@madge --circular --extensions ts,tsx ./ || true
@yarn build

# Test targets
test:
@yarn test

test-ui:
@yarn test:ui

test-coverage:
@yarn test:coverage
@echo "Coverage report generated in coverage/"

test-e2e:
@yarn test:e2e

test-e2e-ui:
@yarn test:e2e:ui

test-e2e-debug:
@yarn test:e2e:debug

# Run all tests (unit + E2E)
test-all: test-coverage test-e2e
@echo "All tests completed!"

# Run tests in CI mode (non-interactive)
test-ci: test-coverage test-e2e
@echo "CI tests completed!"

.PHONY: check test test-ui test-coverage test-e2e test-e2e-ui test-e2e-debug test-all test-ci
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { globalIgnores, defineConfig } from 'eslint/config'

export default defineConfig([
// ignore build output (replacement for your globalIgnores(['dist']))
globalIgnores(["dist"]),
globalIgnores(["dist", "tests"]),

// base configs
js.configs.recommended,
Expand Down
22 changes: 19 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
{
"name": "calf",
"private": true,
"version": "0.4.0",
"version": "0.4.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
Expand All @@ -25,17 +31,27 @@
},
"devDependencies": {
"@eslint/js": "^9.35",
"@playwright/test": "^1.48.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^25.0.3",
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.3",
"@vitest/coverage-v8": "^4.0.0",
"@vitest/ui": "^4.0.0",
"eslint": "^9.35.0",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.4.0",
"jsdom": "^27.0.0",
"rollup-plugin-visualizer": "^6.0.3",
"typescript": "~5.9.2",
"typescript-eslint": "^8.44.0",
"vite": "^7.1.5"
"vite": "^7.1.5",
"vitest": "^4.0.0"
},
"resolutions": {
"@internationalized/date": "3.9.0"
Expand Down
76 changes: 76 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['html'],
['json', { outputFile: 'test-results/results.json' }],
['junit', { outputFile: 'test-results/junit.xml' }],
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Screenshot on failure */
screenshot: 'only-on-failure',
},

/* Increase timeout for slower environments */
timeout: 60000,

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
to24Hour,
toLocaleTimeFormat,
isLink,
urlPrefix,
} from './helpers';
import { initialForm, parseStandardParams } from './eventForm';
import { CalendarDate } from '@internationalized/date';
Expand All @@ -44,7 +45,6 @@ const FixedLabel = ({ children }: { children: React.ReactNode }) => (
function App() {
const [form, setForm] = useState(initialForm);
const [step, setStep] = useState<'form' | 'share'>('form');
const urlPrefix = import.meta.env.MODE === 'production' ? '/calf' : '';
const origin = window.location.origin;
// controlled input for Autocomplete so default timeZone is visible
// date values are managed via HeroUI DateInput onChange and stored in form
Expand Down Expand Up @@ -193,7 +193,6 @@ function App() {
if (typeof obj.eTime === 'string') mapped.eTime = obj.eTime;
if (typeof obj.timezone === 'string') mapped.timezone = obj.timezone;
if (typeof obj.isAllDay === 'boolean') mapped.isAllDay = obj.isAllDay;

// parse date strings like YYYY-MM-DD into CalendarDate
if (typeof obj.sDate === 'string') {
const parts = obj.sDate.split('-').map(Number);
Expand Down Expand Up @@ -410,6 +409,7 @@ function App() {
allowsCustomValue
isClearable={false}
defaultSelectedKey={form.sTime}
inputValue={toLocaleTimeFormat(form.sTime)}
isVirtualized={false}
onInputChange={onChangeStartTime}
>
Expand Down
Loading
Loading