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
32 changes: 32 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,39 @@ on:
types: [published]

jobs:
# Runs the full test suite against React 17 and 18 before any publish step.
# Both matrix jobs must pass — if any fail, the publish job never runs.
# React 19 is excluded until pull-to-refresh setState/act compatibility is fixed.
validate:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
react: ['17', '18']
steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Swap to React ${{ matrix.react }}
run: |
yarn add --dev react@${{ matrix.react }} react-dom@${{ matrix.react }}

- name: Type check
run: yarn ts-check

- name: Unit tests
run: yarn test --runInBand

publish:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 22.x]
node-version: [20.x, 22.x]

steps:
- uses: actions/checkout@v4
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Bundle Size

on: [pull_request]

jobs:
size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

# Posts a PR comment showing the gzip size diff vs the base branch.
# Does not require size-limit to be installed on the base branch —
# measures the built artifact sizes directly.
- uses: preactjs/compressed-size-action@v2
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
pattern: 'dist/{index.es.js,index.js}'
build-script: 'build'
70 changes: 70 additions & 0 deletions .github/workflows/test-build-artifacts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Test Build Artifacts

on:
push:
branches: [master, 'feat/**', 'fix/**', 'ci/**']
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
format: [cjs, esm]

name: Artifact — ${{ matrix.format }}

steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build
run: yarn build

- name: Smoke test CJS artifact
if: matrix.format == 'cjs'
run: |
node -e "
const m = require('./dist/index.js');
const component = m.default || m;
if (typeof component !== 'function') {
console.error('CJS default export is not a function, got:', typeof component);
process.exit(1);
}
console.log('CJS OK — default export is a function');
"

- name: Smoke test ESM artifact
if: matrix.format == 'esm'
run: |
# Verify ES module syntax is present (export keyword) and file is non-empty.
# A full dynamic import would require React as an ESM peer which varies by version,
# so we validate structure and let the React-version matrix cover runtime behaviour.
if ! grep -q "^export " dist/index.es.js; then
echo "ESM artifact missing top-level export statement"
exit 1
fi
echo "ESM OK — top-level export found"
node -e "
const fs = require('fs');
const size = fs.statSync('./dist/index.es.js').size;
if (size < 100) { console.error('ESM artifact suspiciously small:', size, 'bytes'); process.exit(1); }
console.log('ESM artifact size OK:', size, 'bytes');
"

- name: Type declarations exist
run: |
if [ ! -f dist/index.d.ts ]; then
echo "dist/index.d.ts is missing"
exit 1
fi
echo "Type declarations OK"
42 changes: 42 additions & 0 deletions .github/workflows/test-react-versions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Test React Versions

on:
push:
branches: [master, 'feat/**', 'fix/**', 'ci/**']
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- react: '17'
- react: '18'

name: React ${{ matrix.react }}

steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

# Swap the React version before running tests.
# This also catches an overly-narrow peerDependencies range at install time —
# e.g. "^17" blocks React 18/19 and yarn add will fail here first.
- name: Swap to React ${{ matrix.react }}
run: yarn add --dev react@${{ matrix.react }} react-dom@${{ matrix.react }}

- name: Type check
run: yarn ts-check

- name: Unit tests
run: yarn test --runInBand
48 changes: 48 additions & 0 deletions .github/workflows/test-ts-versions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Test TypeScript Versions

on:
push:
branches: [master, 'feat/**', 'fix/**', 'ci/**']
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# 4.9 is the baseline pinned in devDependencies.
# 4.5/4.7 omitted — transitive @types/node uses accessor syntax requiring TS 4.9+,
# causing parse errors that skipLibCheck cannot suppress.
typescript: ['4.9', '5.0', '5.4', 'latest']

name: TypeScript ${{ matrix.typescript }}

steps:
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Swap to TypeScript ${{ matrix.typescript }}
run: yarn add --dev typescript@${{ matrix.typescript }}

# Type-check the library source only (tsconfig.lib.json excludes __tests__).
# Test files need @types/jest globals which changed resolution in TS 6+,
# so we validate the public API surface in isolation.
# TS 6.x deprecated moduleResolution:node — pass ignoreDeprecations via CLI
# (it's a TS 5.0+ option so cannot live in tsconfig.json for our 4.9 matrix job).
- name: Type check library source
run: |
TS_MAJOR=$(npx tsc --version | grep -oE '[0-9]+' | head -1)
if [ "$TS_MAJOR" -ge 6 ]; then
npx tsc -p tsconfig.lib.json --noEmit --ignoreDeprecations 6.0
else
npx tsc -p tsconfig.lib.json --noEmit
fi
15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "7.0.1",
"description": "An Infinite Scroll component in react.",
"engines": {
"node": ">=18.18.0"
"node": ">=20.0.0"
},
"source": "src/index.tsx",
"main": "dist/index.js",
Expand All @@ -22,6 +22,7 @@
"prettify": "prettier --write 'src/**/*'",
"ts-check": "tsc -p tsconfig.json --noEmit",
"test": "jest",
"size": "size-limit",
"prepare": "husky"
},
"repository": {
Expand Down Expand Up @@ -51,6 +52,7 @@
"@babel/preset-env": "^7.28.5",
"@babel/preset-react": "^7.28.5",
"@babel/preset-typescript": "^7.28.5",
"@size-limit/preset-small-lib": "^12.0.1",
"@storybook/addon-essentials": "^7.6.0",
"@storybook/react": "^7.6.0",
"@storybook/react-webpack5": "^7.6.0",
Expand All @@ -76,13 +78,24 @@
"rollup": "^1.26.3",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.25.2",
"size-limit": "^12.0.1",
"storybook": "^7.6.0",
"ts-jest": "^29.4.6",
"typescript": "^4.9.0"
},
"dependencies": {
"throttle-debounce": "^2.1.0"
},
"size-limit": [
{
"path": "dist/index.es.js",
"limit": "6 kB"
},
{
"path": "dist/index.js",
"limit": "6 kB"
}
],
"lint-staged": {
"*.{js,css,json,md}": [
"prettier --write"
Expand Down
42 changes: 42 additions & 0 deletions src/__tests__/package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export {};

/**
* Validates package.json fields that affect consumers at install time.
* These checks run on every `yarn test` invocation — no extra infrastructure needed.
*
* Issue class caught: overly-narrow peerDependency ranges (e.g. "^17" instead of ">=17")
* that block React 18/19 consumers at npm install, like #419.
*/

// eslint-disable-next-line @typescript-eslint/no-require-imports
const pkg = require('../../package.json') as {
peerDependencies: Record<string, string>;
devDependencies: Record<string, string>;
};

describe('package.json — peer dependency ranges', () => {
it('react peer dep uses >= (open-ended), not ^ (caret-bounded)', () => {
// "^17.0.0" resolves to >=17 <18 — blocks React 18/19 consumers at install time.
// Must be ">=17.0.0" or similar open range.
expect(pkg.peerDependencies.react).toMatch(/^>=/);
});

it('react-dom peer dep uses >= (open-ended), not ^ (caret-bounded)', () => {
expect(pkg.peerDependencies['react-dom']).toMatch(/^>=/);
});

it('peer dep floor is not higher than the version we test against in devDependencies', () => {
// Guards against bumping the peer dep floor without updating our dev/test version.
// e.g. peerDep ">=19" while devDep is still "^17" would mean our tests don't cover
// the minimum version we claim to support.
const peerFloor = parseInt(
/\d+/.exec(pkg.peerDependencies.react)?.[0] ?? '',
10
);
const devMajor = parseInt(
/\d+/.exec(pkg.devDependencies.react)?.[0] ?? '',
10
);
expect(peerFloor).toBeLessThanOrEqual(devMajor);
});
});
5 changes: 5 additions & 0 deletions tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["src/index.tsx", "src/utils/**/*"],
"exclude": ["node_modules", "dist", "src/__tests__"]
}
Loading
Loading