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
16 changes: 14 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,42 @@ jobs:
- name: Type check
run: npm run check

# Samples are outside `astro check`. Their own tsconfigs cannot see the
# IG packages' types, so this checks them all with one shared config.
- name: Type check samples
run: npm run check:samples

# A failed check above must not hide a broken build or smoke test, so
# the build runs regardless, and the smoke tests whenever it succeeds.
- name: Build
id: build
if: ${{ !cancelled() }}
run: npm run build

# Browser binaries are cached per Playwright version; OS deps (apt) are
# not cacheable and are installed on every run.
- name: Get Playwright version
id: playwright
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
run: echo "version=$(npx playwright --version | awk '{print $2}')" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browser
id: playwright-cache
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }}

# Headless tests only need the headless shell, not full Chromium.
- name: Install Playwright browser
if: steps.playwright-cache.outputs.cache-hit != 'true'
if: ${{ !cancelled() && steps.build.outcome == 'success' && steps.playwright-cache.outputs.cache-hit != 'true' }}
run: npx playwright install --with-deps --only-shell chromium

- name: Install Playwright OS deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
if: ${{ !cancelled() && steps.build.outcome == 'success' && steps.playwright-cache.outputs.cache-hit == 'true' }}
run: npx playwright install-deps chromium

- name: Smoke tests
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
run: npm run test:smoke
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ Use the same `BASE_PATH` for the build and the tests. CI builds and tests with `
- the sample appears in the sidebar
- it loads without errors in the browser console

4. Commit, push, and open a pull request targeting `vnext`. Include a screenshot of the running sample.
4. Type-check it along with every other sample (CI runs the same check):
```bash
npm run check:samples
```

5. Commit, push, and open a pull request targeting `vnext`. Include a screenshot of the running sample.

## Updating Ignite UI Package Versions

Expand All @@ -156,6 +161,7 @@ Do **not** edit version strings in `package.json` files by hand; CI fails when a
| `npm run preview` | Serve `dist/` on port 4200 |
| `npm run test:smoke` | Run the Playwright smoke tests against the production build |
| `npm run check` | Type-check `src/` and `tests/` with `astro check` |
| `npm run check:samples` | Type-check every published sample, except the generated ones listed in `scripts/lib/skip-type-check.json` |
| `npm run add:sample` | Scaffold `samples/{group}/{component}/{name}/` |
| `npm run update:ig` | Apply `scripts/lib/versions.js` to every `package.json` |
| `npm run generate:code-viewer` | Regenerate `public/code-viewer/**/*.json` |
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"preview": "astro preview --port 4200",
"build:preview": "npm run build && npm run preview",
"check": "astro check",
"check:samples": "node scripts/check-samples.js",
"generate:code-viewer": "node scripts/generate-code-viewer.js",
"update:ig": "node scripts/update-ig.js",
"test:smoke": "playwright test"
Expand Down
2 changes: 2 additions & 0 deletions samples/editors/x-date-picker/date-limits/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ export default class DatePickerDateLimits extends React.Component<any, any> {
}
}

// rendering above class to the React DOM
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<DatePickerDateLimits/>);
2 changes: 2 additions & 0 deletions samples/editors/x-date-picker/editing/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ export default class DatePickerEditing extends React.Component<any, any> {
}
}

// rendering above class to the React DOM
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<DatePickerEditing/>);
2 changes: 2 additions & 0 deletions samples/editors/x-date-picker/format/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,6 @@ export default class DatePickerFormat extends React.Component<any, any> {
}
}

// rendering above class to the React DOM
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<DatePickerFormat/>);
2 changes: 1 addition & 1 deletion samples/grids/grid-lite/sort-config-events/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const getTimeStamp = (): string => `[${new Date().toLocaleTimeString()}]`;
// Define cellTemplate function outside component
const ratingCellTemplate = (ctx: any) => {
return (
<IgrRating readonly={true} step={0.01} value={ctx.value}></IgrRating>
<IgrRating readOnly={true} step={0.01} value={ctx.value}></IgrRating>
);
};

Expand Down
2 changes: 1 addition & 1 deletion samples/grids/grid-lite/sort-config-pipeline/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "./index.css";
// Define cellTemplate function outside component
const ratingCellTemplate = (ctx: any) => {
return (
<IgrRating readonly={true} step={0.01} value={ctx.value}></IgrRating>
<IgrRating readOnly={true} step={0.01} value={ctx.value}></IgrRating>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { html } from 'lit-html';
import "igniteui-webcomponents/themes/light/bootstrap.css";
import "./index.scss";

declare global {
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'igc-select': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
Expand Down
2 changes: 1 addition & 1 deletion samples/grids/grid/editing-excel-style/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import NwindData from "./NwindData.json";
import "igniteui-react-grids/grids/themes/light/bootstrap.css";

function Sample() {
const gridRef = useRef<IgrGrid>();
const gridRef = useRef<IgrGrid>(null);
useEffect(() => {
gridRef.current.addEventListener("keydown", handleKeyDown);
return () => {
Expand Down
2 changes: 1 addition & 1 deletion samples/grids/grid/localization/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const GridLocalizationSample: React.FC = () => {
<IgrGridToolbarTitle className="grid-toolbar-title">Grid with Localization</IgrGridToolbarTitle>
<IgrSelect
value={locale}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setLocale(e.target.value)}
onChange={(e) => setLocale(e.detail.value)}
>
<IgrSelectItem value="hi"><span>HI</span></IgrSelectItem>
<IgrSelectItem value="bg"><span>BG</span></IgrSelectItem>
Expand Down
1 change: 0 additions & 1 deletion samples/inputs/badge/dot/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export default function BadgeDot(): JSX.Element {
<IgrAvatar
src="https://dl.infragistics.com/x/img/avatars/avatar-profile-04.png"
shape="circle"
size="small"
/>
<IgrBadge dot={true} outlined={true} variant="danger" />
</div>
Expand Down
2 changes: 1 addition & 1 deletion samples/inputs/badge/icon/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function BadgeIcon(): JSX.Element {
))}
<div className="badge-icon-item">
<div className="icon-wrapper avatar-wrapper">
<IgrAvatar initials="AZ" shape="circle" size="small" />
<IgrAvatar initials="AZ" shape="circle" />
<IgrBadge variant="danger" outlined={true}>
<IgrIcon name="close" collection="material" />
</IgrBadge>
Expand Down
1 change: 0 additions & 1 deletion samples/inputs/badge/overview/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export default function BadgeOverview(): JSX.Element {
<IgrAvatar
src="https://dl.infragistics.com/x/img/avatars/avatar-profile-04.png"
shape="circle"
size="small"
/>
<IgrBadge outlined={true} variant="success">
<IgrIcon name="check" collection="material" />
Expand Down
8 changes: 4 additions & 4 deletions samples/inputs/badge/tailwind-styling/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,27 @@ export default function BadgeTailwindStyling(): JSX.Element {
return (
<div className="badge-parent flex items-center justify-center !gap-12">
<div className="relative flex">
<IgrAvatar initials="AZ" shape="rounded" size="small" />
<IgrAvatar initials="AZ" shape="rounded" />
<IgrBadge outlined={true} className="badge-style badge-close">
<IgrIcon name="close" collection="material" />
</IgrBadge>
</div>
<div className="relative flex">
<IgrAvatar shape="rounded" size="small">
<IgrAvatar shape="rounded">
<IgrIcon name="person" collection="material" />
</IgrAvatar>
<IgrBadge outlined={true} className="badge-style badge-volume">
<IgrIcon name="volume_off" collection="material" />
</IgrBadge>
</div>
<div className="relative flex">
<IgrAvatar initials="AZ" shape="circle" size="small" />
<IgrAvatar initials="AZ" shape="circle" />
<IgrBadge outlined={true} className="badge-style badge-remove">
<IgrIcon name="remove" collection="material" />
</IgrBadge>
</div>
<div className="relative flex">
<IgrAvatar shape="square" size="small">
<IgrAvatar shape="square">
<IgrIcon name="person" collection="material" />
</IgrAvatar>
<IgrBadge outlined={true} className="badge-style badge-check">
Expand Down
3 changes: 0 additions & 3 deletions samples/inputs/badge/type/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export default function BadgeType(): JSX.Element {
<IgrAvatar
src="https://dl.infragistics.com/x/img/avatars/avatar-profile-04.png"
shape="circle"
size="small"
/>
<IgrBadge dot={true} outlined={true} variant="success" className="dot-badge" />
</div>
Expand All @@ -30,7 +29,6 @@ export default function BadgeType(): JSX.Element {
<IgrAvatar
src="https://dl.infragistics.com/x/img/avatars/avatar-profile-04.png"
shape="circle"
size="small"
/>
<IgrBadge outlined={true} variant="success">
<IgrIcon name="check" collection="material" />
Expand All @@ -43,7 +41,6 @@ export default function BadgeType(): JSX.Element {
<IgrAvatar
src="https://dl.infragistics.com/x/img/avatars/avatar-profile-04.png"
shape="circle"
size="small"
/>
<IgrBadge outlined={true} variant="success">
2
Expand Down
2 changes: 1 addition & 1 deletion samples/inputs/button-group/custom-toggle/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ icons.forEach(([name, text]) => registerIconFromText(name, text, 'material'));

export default function ButtonGroupCustomToggle() {
return (
<IgrButtonGroup selectionMode="multi">
<IgrButtonGroup selection="multiple">
<IgrToggleButton selected={true}><IgrIcon name="border_top" collection="material" /></IgrToggleButton>
<IgrToggleButton><IgrIcon name="border_right" collection="material" /></IgrToggleButton>
<IgrToggleButton><IgrIcon name="border_bottom" collection="material" /></IgrToggleButton>
Expand Down
2 changes: 1 addition & 1 deletion samples/inputs/color-picker/styling/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default function ColorPickerStyling() {
<div className="toolbar-group">
<span className="toolbar-group-label">Colors</span>
<div className="toolbar-actions">
<IgrColorPicker mode="input" placeholder="Color" />
<IgrColorPicker mode="input" />
</div>
</div>
</div>
Expand Down
4 changes: 0 additions & 4 deletions samples/inputs/color-picker/tailwind-styling/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export default function ColorPickerTailwindStyling() {
className={pickerClassName}
id="textPicker"
value={text}
size="small"
label="Text"
onInput={(e: CustomEvent<string>) => setText(e.detail)}
>
Expand All @@ -98,7 +97,6 @@ export default function ColorPickerTailwindStyling() {
className={pickerClassName}
id="accentPicker"
value={accent}
size="small"
label="Accent"
onInput={(e: CustomEvent<string>) => setAccent(e.detail)}
>
Expand All @@ -112,7 +110,6 @@ export default function ColorPickerTailwindStyling() {
className={pickerClassName}
id="borderPicker"
value={border}
size="small"
label="Border"
onInput={(e: CustomEvent<string>) => setBorder(e.detail)}
>
Expand All @@ -126,7 +123,6 @@ export default function ColorPickerTailwindStyling() {
className={pickerClassName}
disabled={true}
value="#7d91ab"
size="small"
label="Shadow (Pro)"
>
<span slot="helper-text" className="swatch-hint">
Expand Down
2 changes: 1 addition & 1 deletion samples/interactions/chat/features/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ igc-avatar::part(base) {

useEffect(() => {
let mounted = true;
createChatMarkdownRenderer().then(renderer => {
createChatMarkdownRenderer({}).then(renderer => {
if (!mounted) return;

setOptions((prev: any) => ({
Expand Down
2 changes: 1 addition & 1 deletion samples/scheduling/date-range-picker/slots/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Down = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -
const Upload = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M440-200h80v-167l64 64 56-57-160-160-160 160 57 56 63-63v167ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>';

export default function DrpSlots() {
const dateRangeRef = useRef<IgrDateRangePicker>();
const dateRangeRef = useRef<IgrDateRangePicker>(null);

const today: Date = new Date();
let tomorrow: Date = new Date();
Expand Down
83 changes: 83 additions & 0 deletions scripts/check-samples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env node
/**
* check-samples.js: type-check every published sample in one TypeScript run.
*
* WHY not each sample's own tsconfig
* ──────────────────────────────────
* Most samples resolve modules with `moduleResolution: node`, which cannot
* see the types the IG packages publish only through `exports`. Every IG
* component then types as `any`, so a prop the component does not have, e.g.
* <IgrColorPicker placeholder="…">, passes unnoticed. tsconfig.samples.json
* checks all of them with bundler resolution instead; one program over every
* sample takes well under a minute, where ~1000 `tsc -p` runs take an hour.
*
* Any type error fails the check. Skipped: samples in lib/unpublished.json,
* as everywhere else, and in lib/skip-type-check.json, generated samples
* whose fix has to land in igniteui-xplat-examples.
*/
import path from 'node:path';
import ts from 'typescript';

import { REPO_ROOT, UNPUBLISHED } from './lib/samples.js';
import skipTypeCheck from './lib/skip-type-check.json' with { type: 'json' };

const CONFIG = path.join(REPO_ROOT, 'tsconfig.samples.json');
const SKIPPED = new Set([...UNPUBLISHED, ...Object.keys(skipTypeCheck.samples)]);

// samples/<group>/<component>/<name>/… → <group>/<component>/<name>
const SLUG_RE = /^samples\/([^/]+\/[^/]+\/[^/]+)\//;

/** The sample slug a file belongs to, or undefined outside samples/. */
function slugOf(fileName) {
const rel = path.relative(REPO_ROOT, fileName).replace(/\\/g, '/');
return rel.match(SLUG_RE)?.[1];
}

const formatHost = {
getCanonicalFileName: f => f,
getCurrentDirectory: () => REPO_ROOT,
getNewLine: () => '\n',
};
// Pretty output in a terminal, one line per error in CI logs (as tsc does).
const format = process.stdout.isTTY ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics;

function report(diagnostics) {
console.error(format(diagnostics, formatHost));
}

function run() {
const { config, error } = ts.readConfigFile(CONFIG, ts.sys.readFile);
if (error) {
report([error]);
process.exit(1);
}

const parsed = ts.parseJsonConfigFileContent(config, ts.sys, REPO_ROOT, undefined, CONFIG);
if (parsed.errors.length) {
report(parsed.errors);
process.exit(1);
}

const fileNames = parsed.fileNames.filter(f => !SKIPPED.has(slugOf(f)));
const checked = new Set(fileNames.map(slugOf).filter(Boolean));
const program = ts.createProgram(fileNames, parsed.options);

const diagnostics = ts.getPreEmitDiagnostics(program);
const failing = [...new Set(diagnostics.map(d => d.file && slugOf(d.file.fileName)).filter(Boolean))].sort();

if (diagnostics.length) {
report(diagnostics);
}

console.log(`Type-checked ${checked.size} sample(s).`);

if (failing.length) {
console.error(`\n${failing.length} sample(s) have type errors:`);
failing.forEach(slug => console.error(` ${slug}`));
}
if (diagnostics.length) {
process.exit(1);
}
}

run();
14 changes: 14 additions & 0 deletions scripts/lib/sample-globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Globals for tsconfig.samples.json only.
*
* Samples generated by igniteui-xplat-examples annotate render() with the
* global JSX namespace, which @types/react 19 removed in favor of React.JSX.
* Restore the one member they use, so the check reports real errors only.
*/
import type { JSX as ReactJSX } from 'react';

declare global {
namespace JSX {
type Element = ReactJSX.Element;
}
}
10 changes: 10 additions & 0 deletions scripts/lib/skip-type-check.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"//": "Samples `npm run check:samples` skips, with the error each has. All are exported from igniteui-xplat-examples, so a fix made only here is lost at the next export: fix the source there, then remove the sample from this list.",
"samples": {
"grids/data-grid/column-resizing": "renders <DataGridColumnResizing/>; the component is GridColumnResizing",
"grids/data-grid/row-selection": "root.render() without `const root = ReactDOM.createRoot(...)`",
"grids/grid/row-pinning-extra-column": "rowList is an array; it has no toArray()",
"maps/geo-map/display-azure-imagery": "ref callback returns a value, which React 19 reads as a cleanup function",
"maps/geo-map/display-bing-imagery": "ref callback returns a value, which React 19 reads as a cleanup function"
}
}
Loading
Loading