diff --git a/packages/x-license/src/test-keys.ts b/packages/x-license/src/test-keys.ts index 21bd132e1120a..879e13555f8ba 100644 --- a/packages/x-license/src/test-keys.ts +++ b/packages/x-license/src/test-keys.ts @@ -105,6 +105,15 @@ export const TEST_KEY_PRO_SUBSCRIPTION_FUTURE = export const TEST_KEY_PREMIUM_SUBSCRIPTION_FUTURE = '1c6ea00fadedfcb3f2ef393f1de32f29Tz0xMjMsRT0zMjUzNTEyNjAwMDAwMCxTPXByZW1pdW0sTE09c3Vic2NyaXB0aW9uLFBWPWluaXRpYWwsVD10cnVlLEtWPTI='; +// --- KV=2 Q1-2026 (used by cross-major compat tests) --- + +/** + * Pro annual, Q1-2026, expiry = 3001-01-01. + * orderId: #123, keyVersion: 2 + */ +export const TEST_KEY_PRO_ANNUAL_Q1_2026 = + '10c66120eda205131bf0a2074a3fddc8Tz0xMjMsRT0zMjUzNTEyNjAwMDAwMCxTPXBybyxMTT1hbm51YWwsUFY9UTEtMjAyNixUPXRydWUsS1Y9Mg=='; + // --- Key format v3 --- /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc50701692c81..b7b06daf20b38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2442,6 +2442,31 @@ importers: specifier: 'catalog:' version: 7.3.2(@types/node@24.5.2)(lightningcss@1.32.0)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.1) + test/x-license-compat: + dependencies: + '@mui/x-license': + specifier: workspace:^ + version: link:../../packages/x-license/build + x-license-v5: + specifier: npm:@mui/x-license-pro@5.17.12 + version: '@mui/x-license-pro@5.17.12(@types/react@19.2.14)(react@19.2.5)' + x-license-v6: + specifier: npm:@mui/x-license-pro@6.10.2 + version: '@mui/x-license-pro@6.10.2(@types/react@19.2.14)(react@19.2.5)' + x-license-v7: + specifier: npm:@mui/x-license@7.29.1 + version: '@mui/x-license@7.29.1(@types/react@19.2.14)(react@19.2.5)' + x-license-v8: + specifier: npm:@mui/x-license@8.26.0 + version: '@mui/x-license@8.26.0(@types/react@19.2.14)(react@19.2.5)' + x-license-v9: + specifier: npm:@mui/x-license@9.0.0 + version: '@mui/x-license@9.0.0(@types/react@19.2.14)(react@19.2.5)' + devDependencies: + vitest: + specifier: 'catalog:' + version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@24.5.2)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@27.4.0)(vite@7.3.2(@types/node@24.5.2)(lightningcss@1.32.0)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.1)) + packages: '@acemir/cssom@0.9.31': @@ -4502,6 +4527,14 @@ packages: '@types/react': optional: true + '@mui/types@7.4.12': + resolution: {integrity: sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/types@9.0.0': resolution: {integrity: sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==} peerDependencies: @@ -4510,6 +4543,16 @@ packages: '@types/react': optional: true + '@mui/utils@5.17.1': + resolution: {integrity: sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/utils@6.4.9': resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==} engines: {node: '>=14.0.0'} @@ -4520,6 +4563,16 @@ packages: '@types/react': optional: true + '@mui/utils@7.3.10': + resolution: {integrity: sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@mui/utils@9.0.0': resolution: {integrity: sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg==} engines: {node: '>=14.0.0'} @@ -4534,6 +4587,64 @@ packages: resolution: {integrity: sha512-Kd9h8wNOzBN/s8F7aLAwgE7fNR1rHmf0Qjsu+c0ADxe8dZUFkuh0Q5S0bOdmMCBf8aLcC5JnKtEsRpXJ2/qOmg==} engines: {node: '>=8.3.0'} + '@mui/x-internals@7.29.0': + resolution: {integrity: sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-internals@8.26.0': + resolution: {integrity: sha512-B9OZau5IQUvIxwpJZhoFJKqRpmWf5r0yMmSXjQuqb5WuqM755EuzWJOenY48denGoENzMLT8hQpA0hRTeU2IPA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-internals@9.0.4': + resolution: {integrity: sha512-I84xcPZOEmN29syfAjgsv25kpW7GX9+F7n2xXKEX5C7VdyXITbAW2RomwXt3guud4KwGhkAGuGDPK7Wi20EoCg==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-license-pro@5.17.12': + resolution: {integrity: sha512-UzFaE+9A30kfguCuME0D5zqsItqbHZ3xZwmyrJr8MvZOEoqiJWF4NT4Pvlg2nqaPYt/h81j7sjVFa3KiwT0vbg==} + engines: {node: '>=12.0.0'} + deprecated: In MUI X v7 the name of this package changed to @mui/x-license to reflect the existence of the premium plan. + peerDependencies: + react: ^17.0.2 || ^18.0.0 + + '@mui/x-license-pro@6.10.2': + resolution: {integrity: sha512-Baw3shilU+eHgU+QYKNPFUKvfS5rSyNJ98pQx02E0gKA22hWp/XAt88K1qUfUMPlkPpvg/uci6gviQSSLZkuKw==} + engines: {node: '>=14.0.0'} + deprecated: In MUI X v7 the name of this package changed to @mui/x-license to reflect the existence of the premium plan. + peerDependencies: + react: ^17.0.0 || ^18.0.0 + + '@mui/x-license@7.29.1': + resolution: {integrity: sha512-+6D4/2IVal8Gfzu7iZS0ZJgL5cMes0gES+uK9D8I4rlMjPQ779N/rXYMe42mGQZbZuRpoqwSq6VLQx3Gr3SkLQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-license@8.26.0': + resolution: {integrity: sha512-TcYLQXVRSaKjm9zyAxW3bi/+sGR2afsc32oG9ufBBiuNMvRZDghUZj7cKrem4VEKpC4Afsw2du9euv88TVUE1w==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-license@9.0.0': + resolution: {integrity: sha512-C4ivah9CJDBTJir6m6RX88nbvohK6XUvg7pucqfxM1NkejVHES9YEFKLm3GfTnHpfqsFyI3EEfkGmWciIQ53/Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-telemetry@8.20.0': + resolution: {integrity: sha512-lmtcx620eryHY4uvVSV8F1IC0h2O6VY4Zifkbtq1LFHz75hUvE3JrERhABlo5qfQhZJEZuMTTxC2NDVCcAUI7Q==} + engines: {node: '>=14.0.0'} + + '@mui/x-telemetry@9.0.2': + resolution: {integrity: sha512-Hhl+cJMw/4vY9JcHIqwKZN7gCNrrK4FiarsdpBajNdXX6E/sm+XaUVuF5DBJMLk6mYaGdXY8FpKIVI5gwFUN9A==} + engines: {node: '>=14.0.0'} + '@napi-rs/keyring-darwin-arm64@1.2.0': resolution: {integrity: sha512-CA83rDeyONDADO25JLZsh3eHY8yTEtm/RS6ecPsY+1v+dSawzT9GywBMu2r6uOp1IEhQs/xAfxgybGAFr17lSA==} engines: {node: '>= 10'} @@ -6404,6 +6515,9 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomically@2.1.1: + resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -6870,6 +6984,10 @@ packages: engines: {node: '>=18'} hasBin: true + conf@11.0.2: + resolution: {integrity: sha512-jjyhlQ0ew/iwmtwsS2RaB6s8DBifcE2GYBEaw2SJDUY/slJJbNfY4GlDVzOs/ff8cM/Wua5CikqXgbFl5eu85A==} + engines: {node: '>=14.16'} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -7115,6 +7233,10 @@ packages: dayjs@1.11.20: resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + debounce-fn@5.1.2: + resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==} + engines: {node: '>=12'} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -7256,6 +7378,10 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} + dot-prop@7.2.0: + resolution: {integrity: sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -7325,6 +7451,10 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + envinfo@7.13.0: resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} engines: {node: '>=4'} @@ -8702,6 +8832,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -10894,6 +11027,12 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + stubborn-fs@2.0.0: + resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} + + stubborn-utils@1.0.2: + resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + style-to-js@1.1.19: resolution: {integrity: sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ==} @@ -11648,6 +11787,9 @@ packages: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} + when-exit@2.1.5: + resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -14274,12 +14416,30 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@mui/types@7.4.12(@types/react@19.2.14)': + dependencies: + '@babel/runtime': 7.29.2 + optionalDependencies: + '@types/react': 19.2.14 + '@mui/types@9.0.0(@types/react@19.2.14)': dependencies: '@babel/runtime': 7.29.2 optionalDependencies: '@types/react': 19.2.14 + '@mui/utils@5.17.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/types': 7.2.24(@types/react@19.2.14) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.5 + react-is: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@mui/utils@6.4.9(@types/react@19.2.14)(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 @@ -14292,6 +14452,18 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@mui/utils@7.3.10(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/types': 7.4.12(@types/react@19.2.14) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.5 + react-is: 19.2.4 + optionalDependencies: + '@types/react': 19.2.14 + '@mui/utils@9.0.0(@types/react@19.2.14)(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 @@ -14317,6 +14489,96 @@ snapshots: unzipper: 0.12.1 uuid: 8.3.2 + '@mui/x-internals@7.29.0(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 7.3.10(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-internals@8.26.0(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 7.3.10(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + reselect: 5.1.1 + use-sync-external-store: 1.6.0(react@19.2.5) + transitivePeerDependencies: + - '@types/react' + + '@mui/x-internals@9.0.4(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 9.0.0(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + reselect: 5.1.1 + use-sync-external-store: 1.6.0(react@19.2.5) + transitivePeerDependencies: + - '@types/react' + + '@mui/x-license-pro@5.17.12(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 5.17.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-license-pro@6.10.2(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 5.17.1(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-license@7.29.1(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 7.3.10(@types/react@19.2.14)(react@19.2.5) + '@mui/x-internals': 7.29.0(@types/react@19.2.14)(react@19.2.5) + react: 19.2.5 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-license@8.26.0(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 7.3.10(@types/react@19.2.14)(react@19.2.5) + '@mui/x-internals': 8.26.0(@types/react@19.2.14)(react@19.2.5) + '@mui/x-telemetry': 8.20.0 + react: 19.2.5 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-license@9.0.0(@types/react@19.2.14)(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/utils': 9.0.0(@types/react@19.2.14)(react@19.2.5) + '@mui/x-internals': 9.0.4(@types/react@19.2.14)(react@19.2.5) + '@mui/x-telemetry': 9.0.2 + react: 19.2.5 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-telemetry@8.20.0': + dependencies: + '@babel/runtime': 7.29.2 + '@fingerprintjs/fingerprintjs': 3.4.2 + ci-info: 4.4.0 + conf: 11.0.2 + is-docker: 4.0.0 + node-machine-id: 1.1.12 + + '@mui/x-telemetry@9.0.2': + dependencies: + '@babel/runtime': 7.29.2 + '@fingerprintjs/fingerprintjs': 3.4.2 + ci-info: 4.4.0 + is-docker: 4.0.0 + node-machine-id: 1.1.12 + '@napi-rs/keyring-darwin-arm64@1.2.0': optional: true @@ -16286,6 +16548,11 @@ snapshots: asynckit@0.4.0: {} + atomically@2.1.1: + dependencies: + stubborn-fs: 2.0.0 + when-exit: 2.1.5 + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -16771,6 +17038,17 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 + conf@11.0.2: + dependencies: + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + atomically: 2.1.1 + debounce-fn: 5.1.2 + dot-prop: 7.2.0 + env-paths: 3.0.0 + json-schema-typed: 8.0.2 + semver: 7.7.4 + confbox@0.1.8: {} console-control-strings@1.1.0: {} @@ -17035,6 +17313,10 @@ snapshots: dayjs@1.11.20: {} + debounce-fn@5.1.2: + dependencies: + mimic-fn: 4.0.0 + debug@2.6.9: dependencies: ms: 2.0.0 @@ -17150,6 +17432,10 @@ snapshots: dependencies: is-obj: 2.0.0 + dot-prop@7.2.0: + dependencies: + type-fest: 2.19.0 + dotenv-expand@11.0.7: dependencies: dotenv: 16.6.1 @@ -17211,6 +17497,8 @@ snapshots: env-paths@2.2.1: {} + env-paths@3.0.0: {} + envinfo@7.13.0: {} err-code@2.0.3: {} @@ -18851,6 +19139,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-nice@1.1.4: {} @@ -21765,6 +22055,12 @@ snapshots: strip-json-comments@3.1.1: {} + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} + style-to-js@1.1.19: dependencies: style-to-object: 1.0.12 @@ -22684,6 +22980,8 @@ snapshots: webidl-conversions: 8.0.1 optional: true + when-exit@2.1.5: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/test/x-license-compat/package.json b/test/x-license-compat/package.json new file mode 100644 index 0000000000000..cc8c4fb86699a --- /dev/null +++ b/test/x-license-compat/package.json @@ -0,0 +1,17 @@ +{ + "name": "@mui-x-internal/x-license-compat", + "private": true, + "version": "0.0.0", + "description": "Cross-major compatibility tests for @mui/x-license. Pinned older versions are imported via npm aliases and exercised against the current license-key formats to catch wire-format regressions.", + "dependencies": { + "@mui/x-license": "workspace:^", + "x-license-v5": "npm:@mui/x-license-pro@5.17.12", + "x-license-v6": "npm:@mui/x-license-pro@6.10.2", + "x-license-v7": "npm:@mui/x-license@7.29.1", + "x-license-v8": "npm:@mui/x-license@8.26.0", + "x-license-v9": "npm:@mui/x-license@9.0.0" + }, + "devDependencies": { + "vitest": "catalog:" + } +} diff --git a/test/x-license-compat/verifyLicense.compat.test.ts b/test/x-license-compat/verifyLicense.compat.test.ts new file mode 100644 index 0000000000000..104ac742f4fe9 --- /dev/null +++ b/test/x-license-compat/verifyLicense.compat.test.ts @@ -0,0 +1,241 @@ +// Cross-major license compatibility tests. +// +// The published `@mui/x-license` (and its predecessor `@mui/x-license-pro`) inside an older +// installed version of the X packages is whatever shipped with that major. Once a customer +// installs `@mui/x-data-grid-pro@v6` (etc.), they don't get our latest verifyLicense +// they get the one frozen at v6's release. So when we issue a license today, it has to +// remain readable by every older verifyLicense we still support. +// +// The matrix below covers three axes: +// - x-license major (v5 / v6 / v7 / v8 / v9) — the verifyLicense baked into customer installs +// - currently-issued license format (KV=1 perpetual, KV=2 subscription/annual, KV=3 annual) +// - commercial package the license is presented to (data-grid-pro, charts-pro, tree-view-pro) +// +// All five majors are pinned to specific published versions (see this workspace's +// package.json) so the matrix is locked to actual released contracts, not in-flight +// workspace changes. Pinned v9 was built with `__ALLOW_TEST_LICENSES__` inlined as `false`, +// which would normally reject every `T=true` test fixture; the workspace's vitest config +// patches that single guard at transform time so the pinned v9 verifyLicense runs as if +// test licenses were allowed (see vitest.config.jsdom.mts). +// +// A failing cell is a real backwards-compat regression, either the wire format changed in a +// way an older major can't decode, or we shipped a token an older major doesn't recognize. +// +// ⚠️ KV=3 cells are intentionally marked `it.fails`. KV=3 was introduced in v9 and is unknown +// to v5–v8 verifyLicense, so any KV=3 key returns Invalid for everyone on those older majors. +// We can't remove the KV=3 format from v9+ retroactively because ~36 KV=3 keys have already +// been issued to real customers; they need the v9+ decoder to keep working. +// Going forward, no new KV should be introduced, extend KV=2 with optional tokens instead +// (older parsers silently ignore unknown tokens). The `it.fails` markers exist so this +// regression stays visible in CI and the day older majors are no longer supported, the +// markers can be removed cleanly. + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +import { verifyLicense as verifyLicenseV5, LicenseStatus as STATUS_V5 } from 'x-license-v5'; +import { verifyLicense as verifyLicenseV6, LICENSE_STATUS as STATUS_V6 } from 'x-license-v6'; +import { verifyLicense as verifyLicenseV7, LICENSE_STATUS as STATUS_V7 } from 'x-license-v7'; +import { verifyLicense as verifyLicenseV8, LICENSE_STATUS as STATUS_V8 } from 'x-license-v8'; +import { + verifyLicense as verifyLicenseV9, + LicenseStatus as STATUS_V9, +} from 'x-license-v9/internals'; +import { + TEST_KEY_V1, + TEST_LICENSE_KEY_PRO, + TEST_KEY_PRO_SUBSCRIPTION_FUTURE, + TEST_KEY_PRO_ANNUAL_Q1_2026, + TEST_KEY_PRO_ANNUAL_V3, + TEST_KEY_PRO_ANNUAL_Q1_2026_V3, +} from '@mui/x-license/internals'; + +// `releaseInfo` / `releaseDate` is the base64-encoded ms timestamp the package was built at. +// Pinning it well before any license expiry keeps the perpetual/annual logic from +// short-circuiting these tests on date math. +const RELEASE_INFO = Buffer.from(String(new Date(2018, 0, 0).getTime()), 'utf8').toString('base64'); + +const ACCEPTED_SCOPES: ('pro' | 'premium')[] = ['pro', 'premium']; + +// Pro-tier commercial packages a customer might install. We use only pro packages because +// the test license keys all carry `S=pro`; pointing a pro license at a `-premium` package +// surfaces OutOfScope, which is correct behavior, not a compat issue. +const PRO_PACKAGES = ['x-data-grid-pro', 'x-charts-pro', 'x-tree-view-pro'] as const; + +type Major = 'v5' | 'v6' | 'v7' | 'v8' | 'v9'; +type PackageName = (typeof PRO_PACKAGES)[number]; + +const licenseMajors: ReadonlyArray<{ + name: Major; + call: (licenseKey: string, packageName: PackageName) => unknown; + valid: unknown; +}> = [ + { + name: 'v5', + // v5 takes `acceptedScopes` and ignores any package-name concept; pointing at a + // different commercial package doesn't change v5's behavior, so we just pass it + // through for symmetry of the cell signature. + call: (licenseKey) => + verifyLicenseV5({ + releaseInfo: RELEASE_INFO, + licenseKey, + acceptedScopes: ACCEPTED_SCOPES, + isProduction: true, + }), + valid: STATUS_V5.Valid, + }, + { + name: 'v6', + // v6 also has no packageName concept. + call: (licenseKey) => + verifyLicenseV6({ + releaseInfo: RELEASE_INFO, + licenseKey, + acceptedScopes: ACCEPTED_SCOPES, + }).status, + valid: STATUS_V6.Valid, + }, + { + name: 'v7', + // v7 introduced packageName so it can gate things like `NotAvailableInInitialProPlan`. + call: (licenseKey, packageName) => + verifyLicenseV7({ + releaseInfo: RELEASE_INFO, + licenseKey, + packageName, + }).status, + valid: STATUS_V7.Valid, + }, + { + name: 'v8', + call: (licenseKey, packageName) => + verifyLicenseV8({ + releaseInfo: RELEASE_INFO, + licenseKey, + packageName, + }).status, + valid: STATUS_V8.Valid, + }, + { + name: 'v9', + // v9 reshaped the signature: releaseInfo/packageName moved into `packageInfo`. + // Empty `version` skips the v9-specific upgrade gate; we're verifying wire-format + // compatibility here, not v9's plan-version enforcement. + call: (licenseKey, packageName) => + verifyLicenseV9({ + packageInfo: { releaseDate: RELEASE_INFO, version: '', name: packageName }, + licenseKey, + }).status, + // v9's published `internals` doesn't re-export the LICENSE_STATUS enum at runtime, + // but the value is just the string 'Valid' (same as every other major). + valid: 'Valid' as STATUS_V9, + }, +]; + +// `knownIncompatible` returns true when the major is fundamentally incapable of accepting +// this license format (e.g. v5 never learned `annual`). +// `notAvailableForPackages` lists pro packages that the v7/v8/v9 plan-version gate excludes +// for this license (e.g. an `initial` plan license can't unlock charts-pro/tree-view-pro). +// `expectedToFail` lists majors where this format is known not to decode; cells for those +// majors get `it.fails.each(...)` so the suite stays green while documenting the regression. +type LicenseCase = { + label: string; + key: string; + knownIncompatible?: (major: Major) => boolean; + notAvailableForPackages?: ReadonlyArray; + expectedToFail?: ReadonlySet; +}; + +// v7+ enforces `NotAvailableInInitialProPlan` for charts-pro / tree-view-pro when the +// license has the `initial` plan version (or KV=1, which is treated as initial). v5/v6 +// don't have this gate, so they accept these packages. Excluding these cells keeps +// "Valid" the meaningful expectation rather than NotAvailableInInitialProPlan. +const INITIAL_PLAN_GATED: ReadonlyArray = ['x-charts-pro', 'x-tree-view-pro']; + +// KV=3 keys are unknown to v5/v6/v7/v8, they all return Invalid. v9 understands KV=3, +// so v9 is intentionally absent from this set. +const KV3_BREAKS_ON: ReadonlySet = new Set(['v5', 'v6', 'v7', 'v8']); + +const licenseKeys: LicenseCase[] = [ + { + label: 'KV=1 perpetual', + key: TEST_KEY_V1, + notAvailableForPackages: INITIAL_PLAN_GATED, + }, + { + label: 'KV=2 subscription (initial, far-future)', + key: TEST_KEY_PRO_SUBSCRIPTION_FUTURE, + notAvailableForPackages: INITIAL_PLAN_GATED, + }, + { + label: 'KV=2 annual (Q3-2024)', + key: TEST_LICENSE_KEY_PRO, + // v5's LICENSING_MODELS is ['perpetual', 'subscription'], `annual` was introduced in v6 + // v5 returns Invalid for any annual key. This is a frozen historical limitation of v5, + // not a regression in the wire format we ship today. + knownIncompatible: (major) => major === 'v5', + }, + { + label: 'KV=2 annual (Q1-2026)', + key: TEST_KEY_PRO_ANNUAL_Q1_2026, + knownIncompatible: (major) => major === 'v5', + }, + { + label: 'KV=3 annual (Q3-2024)', + key: TEST_KEY_PRO_ANNUAL_V3, + expectedToFail: KV3_BREAKS_ON, + }, + { + label: 'KV=3 annual (Q1-2026)', + key: TEST_KEY_PRO_ANNUAL_Q1_2026_V3, + expectedToFail: KV3_BREAKS_ON, + }, +]; + +describe('Cross-major license compatibility — every license must be accepted by every supported major', () => { + // Older verifyLicense implementations log "Key version not found" via console.error + // when they encounter a KV they don't understand. Silence it so vitest-fail-on-console + // doesn't mask the real assertion failure. + let consoleErrorSpy: ReturnType; + beforeEach(() => { + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + }); + afterEach(() => { + consoleErrorSpy.mockRestore(); + }); + + const allCells = licenseMajors.flatMap(({ name: major, call, valid }) => + licenseKeys + .filter(({ knownIncompatible }) => !knownIncompatible?.(major)) + .flatMap(({ label, key, notAvailableForPackages, expectedToFail }) => + PRO_PACKAGES.filter((pkg) => !notAvailableForPackages?.includes(pkg)).map((pkg) => ({ + major, + label, + packageName: pkg, + key, + call, + valid, + shouldFail: expectedToFail?.has(major) ?? false, + })), + ), + ); + + const passingCells = allCells.filter((c) => !c.shouldFail); + const knownFailingCells = allCells.filter((c) => c.shouldFail); + + it.each(passingCells)( + '$major accepts $label on $packageName', + ({ key, packageName, call, valid }) => { + expect(call(key, packageName)).to.equal(valid); + }, + ); + + // `it.fails` asserts the test currently fails, keeps the suite green while + // the KV=3-on-older-majors regression exists, and surfaces a real failure + // the moment it gets fixed (so we know to remove these markers). + it.fails.each(knownFailingCells)( + '$major accepts $label on $packageName', + ({ key, packageName, call, valid }) => { + expect(call(key, packageName)).to.equal(valid); + }, + ); +}); diff --git a/test/x-license-compat/vitest.config.jsdom.mts b/test/x-license-compat/vitest.config.jsdom.mts new file mode 100644 index 0000000000000..6c4a0df3e298e --- /dev/null +++ b/test/x-license-compat/vitest.config.jsdom.mts @@ -0,0 +1,36 @@ +import { mergeConfig } from 'vitest/config'; +import sharedConfig from '../../vitest.shared.mts'; +import { getTestName } from '../../scripts/getTestName.mts'; + +export default mergeConfig(sharedConfig, { + plugins: [ + { + name: 'allow-test-licenses-in-pinned-x-license-v9', + // Published v9 was built with `__ALLOW_TEST_LICENSES__` inlined as `false`, which + // means `if (license.isTestKey && !false)` always rejects T=true test fixtures. + // We can't redefine the global at runtime (it's already inlined), so we patch the + // single guard expression in v9's compiled verifyLicense.js to flip `!false` → `!true`, + // letting test keys through. + transform(code: string, id: string) { + if (/verifyLicense\.m?js$/.test(id) && code.includes('license.isTestKey && !false')) { + return { + code: code.replace('license.isTestKey && !false', 'license.isTestKey && !true'), + map: null, + }; + } + return null; + }, + }, + ], + test: { + name: getTestName(import.meta.url), + environment: 'jsdom', + server: { + // Force Vite to run our `transform` hook on v9's source instead of pre-bundling it. + // Match by package name (`@mui/x-license`, since that's what pnpm resolves the alias to). + deps: { + inline: ['@mui/x-license', /@mui[\\/+]x-license/, 'x-license-v9'], + }, + }, + }, +}); diff --git a/vitest.config.mts b/vitest.config.mts index 0331a96860419..be0c4e45672e8 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -37,6 +37,7 @@ const getProjects = () => { ...(fill === 'jsdom' ? [ `docs/vitest.config.${fill}.mts`, + `test/x-license-compat/vitest.config.${fill}.mts`, // We also run node tests when running in jsdom mode `packages/*/vitest.config.node.mts`, ]