@triflare/fx 2.1.0-beta.1 → 2.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -6
- package/product.yml +1 -1
- package/src/build.js +23 -6
- package/src/cli/commands/check.js +3 -1
- package/src/cli/commands/feature.js +54 -5
- package/src/cli/commands/index.js +2 -0
- package/src/cli/commands/lint.js +4 -0
- package/src/cli/commands/test.js +186 -0
- package/src/cli/context.js +3 -1
- package/src/config-edit.js +78 -0
- package/src/config.js +77 -0
- package/src/features.js +89 -3
- package/src/formatter.js +21 -1
- package/src/index.js +21 -0
- package/src/lint/analyze.js +25 -5
- package/src/lint/config-file.js +14 -0
- package/src/merge.js +7 -0
- package/src/parser.js +78 -3
- package/src/project.js +14 -3
- package/src/test/discover.js +82 -0
- package/src/test/parse.js +72 -0
- package/src/test/pattern.js +84 -0
- package/src/test/reporters.js +332 -0
- package/src/test/runner.js +1457 -0
- package/src/vm.js +353 -0
- package/vendor/pm-vm-dist/.fx-build.json +7 -0
- package/vendor/pm-vm-dist/LICENSE +373 -0
- package/vendor/pm-vm-dist/NOTICE +6 -0
- package/vendor/pm-vm-dist/dist/iframe-extension-worker.js +1 -0
- package/vendor/pm-vm-dist/dist/js/extension-worker/extension-worker.7aa98c98b43d49535069.js +1 -0
- package/vendor/pm-vm-dist/dist/media/music/00765be80e1ec0073c7d855eac123b96.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/040abc33bc843ccea91acc82b370a2a5.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/059f32937ff4e15834ad94972c058386.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/0ae12e3be02439395202850a9c469930.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/0ae841a4034e4847205ff93589fe38b7.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/101c38be253a79f22164915a7b41a485.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/1c0f11f5e3bc3a290e3011a863acbf19.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/1e35fc4bf222acbb373be13bef7b494c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/27470aa23024c67716d745d09d1eeb0d.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/2a2c19ccfb24fd1f611133f46859f697.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/2f42c08774845c95c5afe6aa2011660d.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/317cfa5845ae8684414e36059aa217cf.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/32ab039554155f1cc982d06b17c1b268.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/368b67706d2601ccdb29ba0ae4a06c7d.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/418e78ff0f0202a85f8e51ddceb6383b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/44fede772a621f62516dafeec7ba6e76.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/45b502e33be78aca1c76962d8f0c9799.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/4efb38b83c696fde2043ad3770fd6cf5.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/555849284aa06b4716e47d7e12341ab4.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/6072de860d1aed016b4b82379510efc7.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/612b52d7a8538850a26cc9a6adda0451.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/6469f82ff2d352bc85e7528635ca6b98.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/66f88d71bbebdff3c79c315946b77516.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/70ce80e25dd12856990938c55f576862.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/79fb30eddc4dd8362cabe7dc80b59da6.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/7f0e67fcb804175c4169eabf81d21040.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/832a2a264fdeb69057ad7208584a49b5.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/83f40654a5eb9dc81f854c2b6f83422c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/8414e157e2b98d7d4462330c8c4b1cc2.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/8d02b8a9547b47c8506d6c15c913acc1.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/912efc3e11a27b50b879a7ad3bad2eab.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/92771972e8ea5e5e76566218bd193471.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/92e9bc4ec854ae2c3157169fc5ca47a4.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9b36fd7513f2eeab9876bc75c98262c6.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9c571ca77e0ff0d1071c39941677febc.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9d3f26ebd92e02a921b031785dafd6d6.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9f769e9f239b0a1c048486b24cd9e15b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/a5a68e9168e8e677cc15494e8680538b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/a6a3f99085f00a94f2fb5aebfeee2879.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/a6caebbce7ac630fa45cd12fedc920c0.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/b5e23b90e61aaf37f5de98e9ca81fdbb.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/bc4d077743d9bc06d58065fd764f4019.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/bef29ef069663beb6280b70aac6705de.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/c2ba808baa06dac52c1787790396e54c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/ca1b5393c07468dc2be541c24c3cc687.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/cad1541317d9ef4443a8c6325c7df3be.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/cde6740cc2c1d45a800e1237cc86f9a1.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/d448a74cb6caebc801e921ecdfd0c5d8.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/d5e7e578ff2d4a9b14c0d51f33a24299.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/df6c9e67211654be18df433951b021b0.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/dfe095471b15f0935cde612b52c91a3c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/e64996bbc174611c2ad3d04a1434f7a0.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/e9e0e78519e67c9a17563711949e4684.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/eb290db5208d65da146b41d34f22f008.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/ebe739a8e09e50ea00c5f1afacc15d6a.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/ed88e2831000ca0e9dceabb95a346597.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/f059a67cea01cb6e49ba358c05d658c9.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/f3a9a9ae967dd5a330f29aa94d5c4680.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/f7e43cdb9928ae9999bc623f2087ee1a.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/fc4058e26c5069cbf08e741771d1e210.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/fd598784d15d0a4673327e341eb40d3b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/scratch-vm.js +187 -0
- package/vendor/pm-vm-dist/dist/static/assets/0043f23189fb3cbf2021a88959961ece.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/00d2dff9d5ece15a67074374b2027590.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/02996322134b61a0344527cddc61f9bd.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/06e9863f95061349f2ba2e0fb0b58ddc.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0966e5defb1ae50e569fbbc7ebe7f22a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/09995475180591a6c64c8744fbfdf4d8.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0b18f02669375ad2ee80bef8d07ea7ec.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0dcd27b60c185811b19f59d56eaa12f6.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0e8ed599d9875c77d52db36de2b2b33f.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/103fe904046c35630c600cb122492d78.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/10af80546707afae5507edb828dd35a0.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/11c35683a60415bf62ee1a7324a25848.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/129b99ac19024c35228d855f42fb852b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1980303ffc6f5c17c601a9e5992aea77.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1a064f9a752824e781ccf79ab66850ed.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1a87a73db1ba0193283680e37b35754e.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1c4f445589ea1ce7d954fc601f787f95.svg +58 -0
- package/vendor/pm-vm-dist/dist/static/assets/1c8f54c61adf1cee25299833093395f3.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1c9630b745711a0b829841e8555d76e4.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1d1e22600289d252ebda0b9924bf7d3a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1e7ed7b14b056ebd80f434143846fb4e.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/2b0cfb89b2c8d1badb03136fcf911c3b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/2b7b80de8884cc3cf584aa0deb86a59b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/3682b35e452a7f4a5a61167d1632e24a.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/368c582b289e75a3c5299e954c2ad735.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/36ed01ff8cd7eccf19f2606b56f6cd9f.svg +18 -0
- package/vendor/pm-vm-dist/dist/static/assets/39563f3b38b1fdb805323b1f69dace54.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/3fbd690bdae84637e34b8b933a164e3d.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4240a79d13ecf2792f825457194c96d9.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/44c364962f4cb85aef2cdec4f0e01fd4.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/48d66af328a669a1c84c5dc87a4e9a3b.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4c5757837d14e9fca439064c62617ba1.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4c768843c9fa6593074231cd762b83f8.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4ec6148233578e86736548eb8b9952e7.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/51a75dd50afd0bc6022dfc86455be19c.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/57f0f65052d9dac78ee66b11cc696b6a.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5888c4ba6b8a0767f8be5bbc81994397.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/58d62765bbaa69620eb5bcd2c1cbbebb.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5c6533a34175f61a9cab627f671111e1.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5e0704b815761d38242f1b961ce9ef69.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5e80f6caeb6323606a203fbb248fe18a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5f46c8dc3b8cc2296eb73f013dfce3db.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5fe2f38d9b66e0a55b49e717fcd5c001.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/61edf73198617b2326d37f232b8abd6f.otf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/638bc6bde253241454993ae12273970b.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/64bcfe1f13fce38e2ea26280157ff2ce.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/7337f194b0a727f4be98902487dfb615.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/74496d9086d97aaeeafb3085e9957668.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/76e8f03f3f2ac00ab16dd4f2afee63c4.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/78a74345ee778afbd6abe637b4f69efc.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/7a7fdb40997804d4778e2a12bd7599f7.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/7c5a16e0627c64fd62ec597bb72b879d.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/827c57667d965940792a82d82345e075.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/83857346b7c2e6a351c69377e82a3606.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/83f2d48d0c7d1e6be8a509d4d2c6d0e5.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/87a3a62c0627e925b92d3ede2f62b04f.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8996930d8e83f63cc3341172205df460.otf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8bad9bd9d9a0d53b4a0f4e69bf02764a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8c0c02f16572b730d422f96a3ce53e7f.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8ca9997e9ed7534a8d0f349a6125bd24.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8d83d454d2c42614a203322393c23a5b.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8f3259bb74d811177e8e65dcbea39ed4.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8fdeee6df0b53509b7f9d623cab482b3.svg +1 -0
- package/vendor/pm-vm-dist/dist/static/assets/90eaf6469dbf3a808a3d9739cc77358d.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/942d5ca1a892e7dc5156d0ae7b438c1b.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/94b3a496d095dd3b3cfbaabb84a190fd.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/961bfad11616b6f5c951c58499cf267a.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/97aa37df83e2c5c2effa09d6622cd3a7.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/9ad12a9de73f49fb645902987027eb2b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/9ec3449723269a806ec6eaba0f8f6cef.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/9f8e3a1db63c7a8fd976beca7e3546c3.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a02821bc5d19d616d43d55ac10779101.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a05bbf6359407ad7950e115edab178d9.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a0b130dbafdc7958b73d162239053173.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a61fc8393abec3c0d1e813fc01f252ee.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a7c08a472cc8b1c4e0c45ae9b030b414.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/b52b83f63e8482255d46f40ea454fa01.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/b6a8e4a96ffcd83154ad2c5cbcb52751.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/b700cf6f9c368ce49851d287ddd3b63c.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/bacc13779ca01580b89f39dd2118c22a.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/bb59f04e7f0aabe8e142ac1607a03dd6.otf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/bd16b79cacee7f23835efba88ed4aa67.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/c36dd943607003a3229aca77b49d32de.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/c44ce3bfa465281e765f197d6c3ee7ea.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/c60f3f06c5fb4d49df7236aeb2f31a9d.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d434438e5d193118e4aad3a846e1c7ba.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d4b3ce804680eb7d58f6accc7436e807.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d6214ab9abf22f5d5dee060cb72f637f.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d72b98474fc83d6268886779339e242e.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/da1a6ebcd442e9d867b31fb21ce6a289.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/da3126c6a70c4ef84192c33f99996354.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/e27f7cdf42df8b768345ae83fecffe4b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/e6180253665b8248f685c6d96881f75b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/eb64e706eb7a3b324ba50d3279a980b1.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/ecb1cbda3b9c4f29ab971734d97e3faf.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/ed2a96fbdb476efe419246982e357121.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/edd84717633475a8d3db34087123314a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f04f7b0f673ced51710157ce16e49276.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f28b1ecd6fccd6218a837c14b1109da7.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f382f43d667b784d1bcd8cfe7aa6fff0.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f8d8b9d9f1aa45900b786980418219b1.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f974293a11768662b53444715e3439ed.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f9868658d8483a6dbcd600e0226dfeef.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/fad59dd220562492e6722aa8a97f9369.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/fc383956e315b7ee2bd8dd09de311701.mp3 +0 -0
- package/vendor/pm-vm-dist/package.json +3 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-file glob matching.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of Node built-ins so the browser-safe linter can share the same
|
|
5
|
+
* matcher as `fx test`, the build, and the VS Code extension.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Default test-file glob, relative to the project directory. */
|
|
9
|
+
export const DEFAULT_TEST_PATTERN = "test/**/*.test.tfx";
|
|
10
|
+
|
|
11
|
+
const REGEX_SPECIAL = /[.+^${}()|[\]\\]/g;
|
|
12
|
+
|
|
13
|
+
/** Escape regular-expression syntax so a glob is matched literally. */
|
|
14
|
+
function escapeRegex(text) {
|
|
15
|
+
return text.replace(REGEX_SPECIAL, "\\$&");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Translate a small glob (`**`, `*`, `?`) into an anchored regular expression.
|
|
20
|
+
* A double star followed by a slash also matches zero directories, so
|
|
21
|
+
* `test/**\/*.test.tfx` matches `test/score.test.tfx`.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} pattern
|
|
24
|
+
* @returns {RegExp}
|
|
25
|
+
*/
|
|
26
|
+
export function globToRegExp(pattern) {
|
|
27
|
+
const normalized = String(pattern).replace(/\\/g, "/").replace(/^\.\//, "");
|
|
28
|
+
let source = "";
|
|
29
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
30
|
+
const char = normalized[index];
|
|
31
|
+
if (char === "*") {
|
|
32
|
+
if (normalized[index + 1] === "*") {
|
|
33
|
+
index += 1;
|
|
34
|
+
if (normalized[index + 1] === "/") {
|
|
35
|
+
index += 1;
|
|
36
|
+
source += "(?:.*/)?";
|
|
37
|
+
} else {
|
|
38
|
+
source += ".*";
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
source += "[^/]*";
|
|
42
|
+
}
|
|
43
|
+
} else if (char === "?") {
|
|
44
|
+
source += "[^/]";
|
|
45
|
+
} else {
|
|
46
|
+
source += escapeRegex(char);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return new RegExp(`^${source}$`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether a project-relative path (posix separators) is a test file.
|
|
54
|
+
* @param {string} relativePath
|
|
55
|
+
* @param {string} [pattern]
|
|
56
|
+
*/
|
|
57
|
+
export function matchesTestPattern(
|
|
58
|
+
relativePath,
|
|
59
|
+
pattern = DEFAULT_TEST_PATTERN,
|
|
60
|
+
) {
|
|
61
|
+
const normalized = String(relativePath).replace(/\\/g, "/");
|
|
62
|
+
return globToRegExp(pattern).test(normalized);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Whether `file` matches `pattern` relative to `root`.
|
|
67
|
+
*
|
|
68
|
+
* `root` may be absent, in which case the path is matched as given. Both
|
|
69
|
+
* separators are accepted so callers can pass filesystem paths or URIs.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} file
|
|
72
|
+
* @param {string|null|undefined} root
|
|
73
|
+
* @param {string} [pattern]
|
|
74
|
+
*/
|
|
75
|
+
export function isTestPath(file, root, pattern = DEFAULT_TEST_PATTERN) {
|
|
76
|
+
if (!pattern) return false;
|
|
77
|
+
const normalized = String(file).replace(/\\/g, "/");
|
|
78
|
+
const base = root ? String(root).replace(/\\/g, "/").replace(/\/+$/, "") : "";
|
|
79
|
+
const relative =
|
|
80
|
+
base && normalized.startsWith(`${base}/`)
|
|
81
|
+
? normalized.slice(base.length + 1)
|
|
82
|
+
: normalized;
|
|
83
|
+
return matchesTestPattern(relative, pattern);
|
|
84
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test reporters.
|
|
3
|
+
*
|
|
4
|
+
* Every reporter consumes the runner's structured events and owns all
|
|
5
|
+
* formatting, so terminal, JSON, JUnit, and future editors see idential data.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
export const REPORTER_NAMES = ["default", "verbose", "dot", "json", "junit"];
|
|
10
|
+
|
|
11
|
+
const green = (color, text) => (color ? `\x1b[32m${text}\x1b[0m` : text);
|
|
12
|
+
const red = (color, text) => (color ? `\x1b[31m${text}\x1b[0m` : text);
|
|
13
|
+
const yellow = (color, text) => (color ? `\x1b[33m${text}\x1b[0m` : text);
|
|
14
|
+
const dim = (color, text) => (color ? `\x1b[2m${text}\x1b[0m` : text);
|
|
15
|
+
const bold = (color, text) => (color ? `\x1b[1m${text}\x1b[0m` : text);
|
|
16
|
+
|
|
17
|
+
/** Human-readable duration for terminal output. */
|
|
18
|
+
function seconds(ms) {
|
|
19
|
+
if (ms < 1000) return `${ms}ms`;
|
|
20
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Right-align a value in a fixed-width field. */
|
|
24
|
+
function pad(value, width) {
|
|
25
|
+
return String(value).padStart(width);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Summary block printed after a terminal run. */
|
|
29
|
+
function summarize(result) {
|
|
30
|
+
const lines = [];
|
|
31
|
+
const { counts } = result;
|
|
32
|
+
const files = `${dim(true, "Test Files")} ${
|
|
33
|
+
counts.failedFiles ? `${red(true, `${counts.failedFiles} failed`)} | ` : ""
|
|
34
|
+
}${green(true, `${counts.passedFiles} passed`)} (${counts.files})`;
|
|
35
|
+
const tests = `${dim(true, " Tests")} ${
|
|
36
|
+
counts.failed ? `${red(true, `${counts.failed} failed`)} | ` : ""
|
|
37
|
+
}${green(true, `${counts.passed} passed`)} (${counts.tests})`;
|
|
38
|
+
lines.push(files, tests);
|
|
39
|
+
if (counts.pending)
|
|
40
|
+
lines.push(`${dim(true, " Pending")} ${yellow(true, counts.pending)}`);
|
|
41
|
+
if (counts.errors)
|
|
42
|
+
lines.push(`${dim(true, " Errors")} ${red(true, counts.errors)}`);
|
|
43
|
+
const start = new Date(result.startTime);
|
|
44
|
+
lines.push(
|
|
45
|
+
`${dim(true, " Start at")} ${start.toLocaleTimeString([], {
|
|
46
|
+
hour12: false,
|
|
47
|
+
})}`,
|
|
48
|
+
);
|
|
49
|
+
lines.push(`${dim(true, " Duration")} ${seconds(result.durationMs)}`);
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Failure detail blocks for a terminal run, with source locations. */
|
|
54
|
+
function failureBlocks(result, dir) {
|
|
55
|
+
const blocks = [];
|
|
56
|
+
for (const file of result.files) {
|
|
57
|
+
for (const test of file.tests) {
|
|
58
|
+
if (test.status !== "failed" && test.status !== "error") continue;
|
|
59
|
+
if (!test.error) continue;
|
|
60
|
+
const error = test.error;
|
|
61
|
+
// A test file is reported relative to the project, so resolve it before
|
|
62
|
+
// asking for a path relative to the project root.
|
|
63
|
+
const where = error.file
|
|
64
|
+
? `${error.file}${error.line ? `:${error.line}:${error.column ?? 0}` : ""}`
|
|
65
|
+
: path.relative(dir, path.resolve(dir, file.file));
|
|
66
|
+
const label = test.status === "error" ? "ERROR" : "FAIL";
|
|
67
|
+
blocks.push(
|
|
68
|
+
[
|
|
69
|
+
red(true, `${label} ${file.file} > ${test.name}`),
|
|
70
|
+
`${error.name}: ${error.message}`,
|
|
71
|
+
dim(true, `❯ ${where}`),
|
|
72
|
+
].join("\n"),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return blocks;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** JSON document for `fx test --reporter json` and `--json`. */
|
|
80
|
+
export function jsonReport(result, { events = [], warnings = [] } = {}) {
|
|
81
|
+
return {
|
|
82
|
+
schemaVersion: 1,
|
|
83
|
+
command: "test",
|
|
84
|
+
ok: result.ok,
|
|
85
|
+
warnings,
|
|
86
|
+
data: { ...result, events },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Remove XML 1.0-forbidden C0 controls, keeping tab, newline and CR. */
|
|
91
|
+
function stripInvalidXmlChars(text) {
|
|
92
|
+
return [...String(text)]
|
|
93
|
+
.filter((char) => {
|
|
94
|
+
const code = char.charCodeAt(0);
|
|
95
|
+
return code === 0x9 || code === 0xa || code === 0xd || code >= 0x20;
|
|
96
|
+
})
|
|
97
|
+
.join("");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Escape text for use in JUnit attribute values and bodies. */
|
|
101
|
+
function escapeXml(text) {
|
|
102
|
+
// Terminal color sequences in an error message must not make the report
|
|
103
|
+
// unparseable, even when their control characters are escaped.
|
|
104
|
+
return stripInvalidXmlChars(text)
|
|
105
|
+
.replace(/&/g, "&")
|
|
106
|
+
.replace(/</g, "<")
|
|
107
|
+
.replace(/>/g, ">")
|
|
108
|
+
.replace(/"/g, """)
|
|
109
|
+
.replace(/'/g, "'");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** JUnit XML for CI integrations. */
|
|
113
|
+
export function junitReport(result) {
|
|
114
|
+
const diagnostics = result.files.length ? [] : (result.diagnostics ?? []);
|
|
115
|
+
const files = diagnostics.length
|
|
116
|
+
? [
|
|
117
|
+
{
|
|
118
|
+
file: "<project>",
|
|
119
|
+
durationMs: result.durationMs,
|
|
120
|
+
tests: diagnostics.map((diagnostic, index) => ({
|
|
121
|
+
name: `diagnostic ${index + 1}`,
|
|
122
|
+
status: "error",
|
|
123
|
+
durationMs: 0,
|
|
124
|
+
error: diagnostic,
|
|
125
|
+
})),
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
: result.files;
|
|
129
|
+
const errors = files.reduce(
|
|
130
|
+
(total, file) =>
|
|
131
|
+
total + file.tests.filter((test) => test.status === "error").length,
|
|
132
|
+
0,
|
|
133
|
+
);
|
|
134
|
+
const lines = ['<?xml version="1.0" encoding="UTF-8"?>'];
|
|
135
|
+
lines.push(
|
|
136
|
+
`<testsuites name="fx" tests="${result.counts.tests + diagnostics.length}" failures="${result.counts.failed}" errors="${errors}" time="${(result.durationMs / 1000).toFixed(3)}">`,
|
|
137
|
+
);
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
lines.push(
|
|
140
|
+
` <testsuite name="${escapeXml(file.file)}" tests="${file.tests.length}" failures="${file.tests.filter((t) => t.status === "failed").length}" errors="${file.tests.filter((t) => t.status === "error").length}" time="${(file.durationMs / 1000).toFixed(3)}">`,
|
|
141
|
+
);
|
|
142
|
+
for (const test of file.tests) {
|
|
143
|
+
const time = (test.durationMs / 1000).toFixed(3);
|
|
144
|
+
if (test.status === "failed") {
|
|
145
|
+
const message = test.error?.message ?? "failed";
|
|
146
|
+
lines.push(
|
|
147
|
+
` <testcase name="${escapeXml(test.name)}" classname="${escapeXml(file.file)}" time="${time}"><failure message="${escapeXml(message)}" type="${escapeXml(test.error?.name ?? "AssertionError")}">${escapeXml(
|
|
148
|
+
`${test.error?.name ?? "Error"}: ${message}${test.error?.file ? `\n at ${test.error.file}:${test.error.line ?? 0}` : ""}`,
|
|
149
|
+
)}</failure></testcase>`,
|
|
150
|
+
);
|
|
151
|
+
} else if (test.status === "error") {
|
|
152
|
+
const message = test.error?.message ?? "execution error";
|
|
153
|
+
lines.push(
|
|
154
|
+
` <testcase name="${escapeXml(test.name)}" classname="${escapeXml(file.file)}" time="${time}"><error message="${escapeXml(message)}" type="${escapeXml(test.error?.name ?? "Error")}">${escapeXml(message)}</error></testcase>`,
|
|
155
|
+
);
|
|
156
|
+
} else if (test.status === "pending") {
|
|
157
|
+
lines.push(
|
|
158
|
+
` <testcase name="${escapeXml(test.name)}" classname="${escapeXml(file.file)}" time="${time}"><skipped/></testcase>`,
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
lines.push(
|
|
162
|
+
` <testcase name="${escapeXml(test.name)}" classname="${escapeXml(file.file)}" time="${time}"/>`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
lines.push(" </testsuite>");
|
|
167
|
+
}
|
|
168
|
+
lines.push("</testsuites>");
|
|
169
|
+
return lines.join("\n");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @param {object} [options]
|
|
174
|
+
* @param {"default"|"verbose"|"dot"|"json"|"junit"} [options.name]
|
|
175
|
+
* @param {NodeJS.WriteStream} [options.stream]
|
|
176
|
+
* @param {boolean} [options.color]
|
|
177
|
+
* @param {boolean} [options.tty]
|
|
178
|
+
* @param {string} [options.dir] project root, for failure locations
|
|
179
|
+
*/
|
|
180
|
+
export function createTestReporter({
|
|
181
|
+
name = "default",
|
|
182
|
+
stream = process.stdout,
|
|
183
|
+
color = false,
|
|
184
|
+
tty = false,
|
|
185
|
+
dir = process.cwd(),
|
|
186
|
+
} = {}) {
|
|
187
|
+
const events = [];
|
|
188
|
+
let liveLine = "";
|
|
189
|
+
|
|
190
|
+
const write = (text) => stream.write(text);
|
|
191
|
+
const clearLive = () => {
|
|
192
|
+
if (!liveLine) return;
|
|
193
|
+
write("\r\x1b[2K");
|
|
194
|
+
liveLine = "";
|
|
195
|
+
};
|
|
196
|
+
const live = (text) => {
|
|
197
|
+
clearLive();
|
|
198
|
+
liveLine = text;
|
|
199
|
+
write(`\r${text}`);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const onEvent = (event) => {
|
|
203
|
+
if (event.event === "run-start") events.length = 0;
|
|
204
|
+
events.push(event);
|
|
205
|
+
switch (event.event) {
|
|
206
|
+
case "run-start":
|
|
207
|
+
if (name === "default" || name === "verbose")
|
|
208
|
+
write(
|
|
209
|
+
`\n ${bold(color, "RUN")} ${dim(color, `v${event.version}`)} ${event.dir}\n\n`,
|
|
210
|
+
);
|
|
211
|
+
break;
|
|
212
|
+
case "discovered":
|
|
213
|
+
if (name === "default") {
|
|
214
|
+
for (const file of event.files)
|
|
215
|
+
for (const test of file.tests)
|
|
216
|
+
write(
|
|
217
|
+
` ${dim(color, "PENDING")} ${file.file} > ${test.name}\n`,
|
|
218
|
+
);
|
|
219
|
+
} else if (name === "verbose") {
|
|
220
|
+
for (const file of event.files)
|
|
221
|
+
for (const test of file.tests)
|
|
222
|
+
write(
|
|
223
|
+
` ${yellow(color, "↓")} ${file.file} > ${test.name} ${dim(color, test.status)}\n`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case "test-start":
|
|
228
|
+
if (name === "verbose")
|
|
229
|
+
write(
|
|
230
|
+
` ${dim(color, "▸")} ${event.file} > ${event.name} ${dim(color, event.status)}\n`,
|
|
231
|
+
);
|
|
232
|
+
else if (name === "default") {
|
|
233
|
+
if (tty) live(dim(color, ` ▸ ${event.file} > ${event.name}`));
|
|
234
|
+
else
|
|
235
|
+
write(
|
|
236
|
+
` ${dim(color, "RUNNING")} ${event.file} > ${event.name}\n`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case "test-result":
|
|
241
|
+
clearLive();
|
|
242
|
+
if (name === "verbose") {
|
|
243
|
+
const mark =
|
|
244
|
+
event.status === "passed"
|
|
245
|
+
? green(color, "✓")
|
|
246
|
+
: event.status === "failed" || event.status === "error"
|
|
247
|
+
? red(color, "×")
|
|
248
|
+
: yellow(color, "↓");
|
|
249
|
+
write(
|
|
250
|
+
` ${mark} ${event.name} ${dim(color, seconds(event.durationMs))}\n`,
|
|
251
|
+
);
|
|
252
|
+
} else if (name === "default") {
|
|
253
|
+
const mark =
|
|
254
|
+
event.status === "passed"
|
|
255
|
+
? green(color, "✓")
|
|
256
|
+
: event.status === "failed" || event.status === "error"
|
|
257
|
+
? red(color, "×")
|
|
258
|
+
: yellow(color, "↓");
|
|
259
|
+
write(
|
|
260
|
+
` ${mark} ${event.file} > ${event.name} ${dim(color, seconds(event.durationMs))}\n`,
|
|
261
|
+
);
|
|
262
|
+
} else if (name === "dot")
|
|
263
|
+
write(
|
|
264
|
+
event.status === "passed"
|
|
265
|
+
? green(color, "·")
|
|
266
|
+
: event.status === "failed" || event.status === "error"
|
|
267
|
+
? red(color, "×")
|
|
268
|
+
: yellow(color, "↓"),
|
|
269
|
+
);
|
|
270
|
+
break;
|
|
271
|
+
case "file-result": {
|
|
272
|
+
if (name !== "default" && name !== "verbose") break;
|
|
273
|
+
const failed = event.tests.filter((test) => test.status === "failed");
|
|
274
|
+
const errors = event.tests.filter((test) => test.status === "error");
|
|
275
|
+
const detail = [
|
|
276
|
+
`${event.tests.length} ${event.tests.length === 1 ? "test" : "tests"}`,
|
|
277
|
+
...(failed.length ? [`${failed.length} failed`] : []),
|
|
278
|
+
...(errors.length ? [`${errors.length} errors`] : []),
|
|
279
|
+
].join(" | ");
|
|
280
|
+
const mark =
|
|
281
|
+
event.status === "passed" ? green(color, "✓") : red(color, "×");
|
|
282
|
+
write(
|
|
283
|
+
`${mark} ${event.file} ${dim(color, `(${detail}) ${seconds(event.durationMs)}`)}\n`,
|
|
284
|
+
);
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "diagnostic":
|
|
288
|
+
clearLive();
|
|
289
|
+
// JSON and JUnit own the whole stream: a plain diagnostic line would
|
|
290
|
+
// make the document unparseable. Both reporters carry diagnostics in
|
|
291
|
+
// their structured payload instead.
|
|
292
|
+
if (name === "json" || name === "junit") break;
|
|
293
|
+
write(
|
|
294
|
+
`${red(color, "×")} ${event.file ?? "<project>"}${
|
|
295
|
+
event.line ? `:${event.line}` : ""
|
|
296
|
+
} ${event.message}\n`,
|
|
297
|
+
);
|
|
298
|
+
break;
|
|
299
|
+
case "run-end":
|
|
300
|
+
clearLive();
|
|
301
|
+
if (name === "dot") write("\n");
|
|
302
|
+
if (name === "json") {
|
|
303
|
+
write(`${JSON.stringify(jsonReport(event.result, { events }))}\n`);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
if (name === "junit") {
|
|
307
|
+
write(`${junitReport(event.result)}\n`);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
if (name === "default" || name === "verbose") {
|
|
311
|
+
const blocks = failureBlocks(event.result, dir);
|
|
312
|
+
if (blocks.length) {
|
|
313
|
+
const totalErrors =
|
|
314
|
+
event.result.counts.failed + (event.result.counts.errors ?? 0);
|
|
315
|
+
write(`\n${red(color, `⎯⎯⎯ Failed Tests ${totalErrors} ⎯⎯⎯`)}\n\n`);
|
|
316
|
+
write(`${blocks.join("\n\n")}\n`);
|
|
317
|
+
}
|
|
318
|
+
for (const diagnostic of event.result.diagnostics)
|
|
319
|
+
if (!event.result.files.length)
|
|
320
|
+
write(`${red(color, "×")} ${diagnostic.message}\n`);
|
|
321
|
+
write(`\n${summarize(event.result)}\n\n`);
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
default:
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return { name, onEvent, events };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export { pad, seconds };
|