@theia/filesystem 1.70.0 → 1.71.0-next.0
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/lib/browser/file-resource.spec.js +1 -1
- package/lib/browser/file-resource.spec.js.map +1 -1
- package/lib/browser/file-service-watcher.spec.d.ts +2 -0
- package/lib/browser/file-service-watcher.spec.d.ts.map +1 -0
- package/lib/browser/file-service-watcher.spec.js +616 -0
- package/lib/browser/file-service-watcher.spec.js.map +1 -0
- package/lib/browser/file-service.d.ts +23 -0
- package/lib/browser/file-service.d.ts.map +1 -1
- package/lib/browser/file-service.js +253 -12
- package/lib/browser/file-service.js.map +1 -1
- package/lib/node/disk-file-system-provider.spec.js +2 -1
- package/lib/node/disk-file-system-provider.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js +5 -4
- package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js.map +1 -1
- package/package.json +3 -3
- package/src/browser/file-resource.spec.ts +1 -1
- package/src/browser/file-service-watcher.spec.ts +807 -0
- package/src/browser/file-service.ts +295 -14
- package/src/node/disk-file-system-provider.spec.ts +2 -1
- package/src/node/parcel-watcher/parcel-filesystem-watcher.spec.ts +5 -4
|
@@ -63,6 +63,7 @@ import type { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-l
|
|
|
63
63
|
import { EncodingRegistry } from '@theia/core/lib/browser/encoding-registry';
|
|
64
64
|
import { UTF8, UTF8_with_bom } from '@theia/core/lib/common/encodings';
|
|
65
65
|
import { EncodingService, ResourceEncoding, DecodeStreamResult } from '@theia/core/lib/common/encoding-service';
|
|
66
|
+
import { Minimatch } from 'minimatch';
|
|
66
67
|
import { Mutable } from '@theia/core/lib/common/types';
|
|
67
68
|
import { readFileIntoStream } from '../common/io';
|
|
68
69
|
import { FileSystemWatcherErrorHandler } from './filesystem-watcher-error-handler';
|
|
@@ -384,6 +385,7 @@ export class FileService {
|
|
|
384
385
|
return Disposable.create(() => {
|
|
385
386
|
this.onDidChangeFileSystemProviderRegistrationsEmitter.fire({ added: false, scheme, provider });
|
|
386
387
|
this.providers.delete(scheme);
|
|
388
|
+
this.recursiveWatcherIndexes.delete(provider);
|
|
387
389
|
|
|
388
390
|
providerDisposables.dispose();
|
|
389
391
|
});
|
|
@@ -1431,7 +1433,8 @@ export class FileService {
|
|
|
1431
1433
|
return this.onDidFilesChangeEmitter.event;
|
|
1432
1434
|
}
|
|
1433
1435
|
|
|
1434
|
-
private activeWatchers = new Map<string,
|
|
1436
|
+
private activeWatchers = new Map<string, WatcherEntry>();
|
|
1437
|
+
private recursiveWatcherIndexes = new Map<FileSystemProvider, TernarySearchTree<URI, string>>();
|
|
1435
1438
|
|
|
1436
1439
|
watch(resource: URI, options: WatchOptions = { recursive: false, excludes: [] }): Disposable {
|
|
1437
1440
|
const resolvedOptions: WatchOptions = {
|
|
@@ -1460,28 +1463,292 @@ export class FileService {
|
|
|
1460
1463
|
const provider = await this.withProvider(resource);
|
|
1461
1464
|
const key = this.toWatchKey(provider, resource, options);
|
|
1462
1465
|
|
|
1463
|
-
//
|
|
1464
|
-
const
|
|
1465
|
-
if (
|
|
1466
|
-
|
|
1466
|
+
// (A) Exact-match dedup: if the same key already exists, just increment the count
|
|
1467
|
+
const existing = this.activeWatchers.get(key);
|
|
1468
|
+
if (existing) {
|
|
1469
|
+
existing.count++;
|
|
1470
|
+
return this.createWatcherDisposable(key, existing);
|
|
1467
1471
|
}
|
|
1468
1472
|
|
|
1469
|
-
//
|
|
1470
|
-
|
|
1473
|
+
// (B) Check if an existing recursive parent watcher already covers this path
|
|
1474
|
+
const subsumingParentKey = this.findSubsumingParent(provider, resource, options);
|
|
1475
|
+
if (subsumingParentKey) {
|
|
1476
|
+
const parentEntry = this.activeWatchers.get(subsumingParentKey) as RecursiveWatcherEntry;
|
|
1477
|
+
const entry = this.createWatcherEntry(provider, resource, options, false);
|
|
1478
|
+
entry.subsumingParent = parentEntry;
|
|
1479
|
+
this.activeWatchers.set(key, entry);
|
|
1480
|
+
parentEntry.subsumedChildren.add(key);
|
|
1481
|
+
return this.createWatcherDisposable(key, entry);
|
|
1482
|
+
}
|
|
1471
1483
|
|
|
1472
|
-
|
|
1484
|
+
// (C) Create a real OS-level watcher
|
|
1485
|
+
const entry = this.createWatcherEntry(provider, resource, options);
|
|
1486
|
+
this.activeWatchers.set(key, entry);
|
|
1487
|
+
|
|
1488
|
+
// (D) If this is a recursive watcher, index it and subsume existing children
|
|
1489
|
+
if (this.isRecursiveWatcherEntry(entry)) {
|
|
1490
|
+
this.indexRecursiveWatcher(provider, resource, key);
|
|
1491
|
+
this.subsumeExistingChildren(provider, resource, key, entry);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
return this.createWatcherDisposable(key, entry);
|
|
1495
|
+
}
|
|
1473
1496
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1497
|
+
private createWatcherEntry(provider: FileSystemProvider, resource: URI, options: WatchOptions, startWatching: boolean = true): WatcherEntry {
|
|
1498
|
+
const realWatcher = startWatching ? provider.watch(resource, options) : undefined;
|
|
1499
|
+
if (options.recursive) {
|
|
1500
|
+
const recursiveEntry: RecursiveWatcherEntry = {
|
|
1501
|
+
resource, options, provider,
|
|
1502
|
+
count: 1,
|
|
1503
|
+
realWatcher,
|
|
1504
|
+
subsumingParent: undefined,
|
|
1505
|
+
subsumedChildren: new Set(),
|
|
1506
|
+
compiledExcludes: options.excludes.map(pattern => new Minimatch(pattern, { dot: true })),
|
|
1507
|
+
};
|
|
1508
|
+
return recursiveEntry;
|
|
1509
|
+
}
|
|
1510
|
+
return {
|
|
1511
|
+
resource, options, provider,
|
|
1512
|
+
count: 1,
|
|
1513
|
+
realWatcher,
|
|
1514
|
+
subsumingParent: undefined,
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1476
1517
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1518
|
+
private createWatcherDisposable(key: string, entry: WatcherEntry): Disposable {
|
|
1519
|
+
return Disposable.create(() => {
|
|
1520
|
+
entry.count--;
|
|
1521
|
+
if (entry.count === 0) {
|
|
1522
|
+
this.disposeWatcherEntry(key, entry);
|
|
1481
1523
|
}
|
|
1482
1524
|
});
|
|
1483
1525
|
}
|
|
1484
1526
|
|
|
1527
|
+
private disposeWatcherEntry(key: string, entry: WatcherEntry): void {
|
|
1528
|
+
// Unregister from parent if subsumed
|
|
1529
|
+
if (entry.subsumingParent) {
|
|
1530
|
+
entry.subsumingParent.subsumedChildren.delete(key);
|
|
1531
|
+
entry.subsumingParent = undefined;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// If this is a recursive watcher with subsumed children, promote them.
|
|
1535
|
+
// Remove from the index first so that promoted children don't re-parent to this dying entry.
|
|
1536
|
+
// Only remove from the index if this entry has a real watcher — subsumed entries
|
|
1537
|
+
// are never indexed, and removing would corrupt another watcher's index at the same URI.
|
|
1538
|
+
if (this.isRecursiveWatcherEntry(entry)) {
|
|
1539
|
+
if (entry.realWatcher) {
|
|
1540
|
+
this.removeFromRecursiveIndex(entry.provider, entry.resource);
|
|
1541
|
+
}
|
|
1542
|
+
this.promoteSubsumedChildren(entry);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Dispose the real OS watcher if any
|
|
1546
|
+
if (entry.realWatcher) {
|
|
1547
|
+
entry.realWatcher.dispose();
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
this.activeWatchers.delete(key);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
private promoteSubsumedChildren(parentEntry: RecursiveWatcherEntry): void {
|
|
1554
|
+
for (const childKey of parentEntry.subsumedChildren) {
|
|
1555
|
+
const childEntry = this.activeWatchers.get(childKey);
|
|
1556
|
+
if (!childEntry || childEntry.count === 0) {
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
childEntry.subsumingParent = undefined;
|
|
1561
|
+
|
|
1562
|
+
// Try to find another subsuming parent
|
|
1563
|
+
const newParentKey = this.findSubsumingParent(childEntry.provider, childEntry.resource, childEntry.options);
|
|
1564
|
+
if (newParentKey) {
|
|
1565
|
+
const newParent = this.activeWatchers.get(newParentKey) as RecursiveWatcherEntry;
|
|
1566
|
+
childEntry.subsumingParent = newParent;
|
|
1567
|
+
newParent.subsumedChildren.add(childKey);
|
|
1568
|
+
} else {
|
|
1569
|
+
// No parent available — create a real OS watcher
|
|
1570
|
+
childEntry.realWatcher = childEntry.provider.watch(childEntry.resource, childEntry.options);
|
|
1571
|
+
// If this promoted child is recursive, re-index it so later promoted siblings can find it
|
|
1572
|
+
if (this.isRecursiveWatcherEntry(childEntry)) {
|
|
1573
|
+
this.indexRecursiveWatcher(childEntry.provider, childEntry.resource, childKey);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
parentEntry.subsumedChildren.clear();
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
private findSubsumingParent(provider: FileSystemProvider, resource: URI, childOptions: WatchOptions): string | undefined {
|
|
1581
|
+
const tree = this.getRecursiveWatcherIndex(provider);
|
|
1582
|
+
|
|
1583
|
+
const parentKey = tree.findSubstr(resource);
|
|
1584
|
+
if (!parentKey) {
|
|
1585
|
+
return undefined;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
const parentEntry = this.activeWatchers.get(parentKey);
|
|
1589
|
+
if (!parentEntry || !this.isRecursiveWatcherEntry(parentEntry)) {
|
|
1590
|
+
return undefined;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// Check if the child resource is excluded by the parent's exclude patterns
|
|
1594
|
+
if (this.isExcludedByParent(parentEntry, resource)) {
|
|
1595
|
+
return undefined;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// A parent can only subsume a child if the parent's excludes don't filter out
|
|
1599
|
+
// events the child cares about. Every exclude of the parent must also be an
|
|
1600
|
+
// exclude of the child; otherwise the child would silently miss events.
|
|
1601
|
+
if (!this.areExcludesCompatible(parentEntry, childOptions)) {
|
|
1602
|
+
return undefined;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
return parentKey;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
private isExcludedByParent(parentEntry: RecursiveWatcherEntry, childResource: URI): boolean {
|
|
1609
|
+
if (parentEntry.compiledExcludes.length === 0) {
|
|
1610
|
+
return false;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const caseSensitive = !!(parentEntry.provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
|
|
1614
|
+
let parentUri = parentEntry.resource;
|
|
1615
|
+
let childUri = childResource;
|
|
1616
|
+
if (!caseSensitive) {
|
|
1617
|
+
parentUri = parentUri.withPath(parentUri.path.toString().toLowerCase());
|
|
1618
|
+
childUri = childUri.withPath(childUri.path.toString().toLowerCase());
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const relativePath = parentUri.relative(childUri);
|
|
1622
|
+
if (!relativePath) {
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
const relativeStr = relativePath.toString();
|
|
1627
|
+
return parentEntry.compiledExcludes.some(pattern => this.matchesExcludePattern(pattern, relativeStr));
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* Returns true if the parent's excludes are compatible with the child, meaning
|
|
1632
|
+
* the child won't silently miss events it expects. Excludes only matter when
|
|
1633
|
+
* the child is recursive - a non-recursive child only watches its own directory,
|
|
1634
|
+
* and `isExcludedByParent` already handles the case where the child's path itself
|
|
1635
|
+
* is under an excluded directory. For recursive children, every exclude of the
|
|
1636
|
+
* parent must also be an exclude of the child; otherwise the parent's OS watcher
|
|
1637
|
+
* would filter out sub-paths the child cares about.
|
|
1638
|
+
*/
|
|
1639
|
+
private areExcludesCompatible(parentEntry: RecursiveWatcherEntry, childOptions: WatchOptions): boolean {
|
|
1640
|
+
if (!childOptions.recursive) {
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
if (parentEntry.compiledExcludes.length === 0) {
|
|
1644
|
+
return true;
|
|
1645
|
+
}
|
|
1646
|
+
const childExcludes = new Set(childOptions.excludes);
|
|
1647
|
+
return parentEntry.options.excludes.every(e => childExcludes.has(e));
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
private matchesExcludePattern(pattern: Minimatch, relativePath: string): boolean {
|
|
1651
|
+
if (pattern.match(relativePath)) {
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1654
|
+
// Also test ancestor directories — if a directory is excluded, everything under it is excluded
|
|
1655
|
+
const segments = relativePath.split('/');
|
|
1656
|
+
let accumulated = '';
|
|
1657
|
+
for (const segment of segments) {
|
|
1658
|
+
accumulated = accumulated ? accumulated + '/' + segment : segment;
|
|
1659
|
+
if (accumulated !== relativePath && pattern.match(accumulated)) {
|
|
1660
|
+
return true;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
private subsumeExistingChildren(provider: FileSystemProvider, parentResource: URI, parentKey: string, parentEntry: RecursiveWatcherEntry): void {
|
|
1667
|
+
const caseSensitive = !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
|
|
1668
|
+
for (const [childKey, childEntry] of this.activeWatchers.entries()) {
|
|
1669
|
+
if (childKey === parentKey) {
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
if (childEntry.subsumingParent) {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
if (childEntry.provider !== provider) {
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (!parentResource.isEqualOrParent(childEntry.resource, caseSensitive)) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
if (this.isExcludedByParent(parentEntry, childEntry.resource)) {
|
|
1682
|
+
continue;
|
|
1683
|
+
}
|
|
1684
|
+
if (!this.areExcludesCompatible(parentEntry, childEntry.options)) {
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// Subsume: dispose the child's real watcher
|
|
1689
|
+
if (childEntry.realWatcher) {
|
|
1690
|
+
childEntry.realWatcher.dispose();
|
|
1691
|
+
childEntry.realWatcher = undefined;
|
|
1692
|
+
}
|
|
1693
|
+
childEntry.subsumingParent = parentEntry;
|
|
1694
|
+
parentEntry.subsumedChildren.add(childKey);
|
|
1695
|
+
|
|
1696
|
+
// If the child was itself a recursive watcher, re-parent its grandchildren
|
|
1697
|
+
if (this.isRecursiveWatcherEntry(childEntry)) {
|
|
1698
|
+
for (const grandchildKey of childEntry.subsumedChildren) {
|
|
1699
|
+
const grandchild = this.activeWatchers.get(grandchildKey);
|
|
1700
|
+
if (!grandchild) {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
// Check if the grandchild is compatible with the new parent
|
|
1704
|
+
if (this.isExcludedByParent(parentEntry, grandchild.resource) || !this.areExcludesCompatible(parentEntry, grandchild.options)) {
|
|
1705
|
+
// Grandchild can't be subsumed by the new parent — give it a real watcher
|
|
1706
|
+
grandchild.subsumingParent = undefined;
|
|
1707
|
+
grandchild.realWatcher = grandchild.provider.watch(grandchild.resource, grandchild.options);
|
|
1708
|
+
// If the promoted grandchild is recursive, re-index it so future watchers can find it
|
|
1709
|
+
if (this.isRecursiveWatcherEntry(grandchild)) {
|
|
1710
|
+
this.indexRecursiveWatcher(grandchild.provider, grandchild.resource, grandchildKey);
|
|
1711
|
+
}
|
|
1712
|
+
} else {
|
|
1713
|
+
grandchild.subsumingParent = parentEntry;
|
|
1714
|
+
parentEntry.subsumedChildren.add(grandchildKey);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
childEntry.subsumedChildren.clear();
|
|
1718
|
+
// Only remove the child's index entry if it has a different URI than the parent.
|
|
1719
|
+
// The parent was already indexed at its URI, and removing the child's
|
|
1720
|
+
// entry would delete the parent's entry if they share the same URI.
|
|
1721
|
+
if (!childEntry.resource.isEqual(parentResource, caseSensitive)) {
|
|
1722
|
+
this.removeFromRecursiveIndex(provider, childEntry.resource);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
private getRecursiveWatcherIndex(provider: FileSystemProvider): TernarySearchTree<URI, string> {
|
|
1729
|
+
let tree = this.recursiveWatcherIndexes.get(provider);
|
|
1730
|
+
if (!tree) {
|
|
1731
|
+
const caseSensitive = !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
|
|
1732
|
+
tree = TernarySearchTree.forUris<string>(caseSensitive);
|
|
1733
|
+
this.recursiveWatcherIndexes.set(provider, tree);
|
|
1734
|
+
}
|
|
1735
|
+
return tree;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
private indexRecursiveWatcher(provider: FileSystemProvider, resource: URI, key: string): void {
|
|
1739
|
+
const tree = this.getRecursiveWatcherIndex(provider);
|
|
1740
|
+
tree.set(resource, key);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
private removeFromRecursiveIndex(provider: FileSystemProvider, resource: URI): void {
|
|
1744
|
+
const tree = this.getRecursiveWatcherIndex(provider);
|
|
1745
|
+
tree.delete(resource);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
private isRecursiveWatcherEntry(entry: WatcherEntry): entry is RecursiveWatcherEntry {
|
|
1749
|
+
return 'subsumedChildren' in entry;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1485
1752
|
private toWatchKey(provider: FileSystemProvider, resource: URI, options: WatchOptions): string {
|
|
1486
1753
|
return [
|
|
1487
1754
|
this.toMapKey(provider, resource), // lowercase path if the provider is case insensitive
|
|
@@ -1843,3 +2110,17 @@ export class FileService {
|
|
|
1843
2110
|
this.watcherErrorHandler.handleError();
|
|
1844
2111
|
}
|
|
1845
2112
|
}
|
|
2113
|
+
|
|
2114
|
+
interface WatcherEntry {
|
|
2115
|
+
readonly resource: URI;
|
|
2116
|
+
readonly options: WatchOptions;
|
|
2117
|
+
readonly provider: FileSystemProvider;
|
|
2118
|
+
count: number;
|
|
2119
|
+
realWatcher: Disposable | undefined;
|
|
2120
|
+
subsumingParent: RecursiveWatcherEntry | undefined;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
interface RecursiveWatcherEntry extends WatcherEntry {
|
|
2124
|
+
readonly subsumedChildren: Set<string>;
|
|
2125
|
+
readonly compiledExcludes: Minimatch[];
|
|
2126
|
+
}
|
|
@@ -113,7 +113,8 @@ describe('disk-file-system-provider', () => {
|
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
it('delete is able to delete file', async ()
|
|
116
|
+
it('delete is able to delete file', async function (): Promise<void> {
|
|
117
|
+
this.timeout(10000);
|
|
117
118
|
const tempDirPath = tracked.mkdirSync();
|
|
118
119
|
const testFile = join(tempDirPath, 'test.file');
|
|
119
120
|
const testFileUri = FileUri.create(testFile);
|
|
@@ -76,15 +76,15 @@ describe('parcel-filesystem-watcher', function (): void {
|
|
|
76
76
|
|
|
77
77
|
fs.mkdirSync(FileUri.fsPath(root.resolve('foo')));
|
|
78
78
|
expect(fs.statSync(FileUri.fsPath(root.resolve('foo'))).isDirectory()).to.be.true;
|
|
79
|
-
await sleep(
|
|
79
|
+
await sleep(2000);
|
|
80
80
|
|
|
81
81
|
fs.mkdirSync(FileUri.fsPath(root.resolve('foo').resolve('bar')));
|
|
82
82
|
expect(fs.statSync(FileUri.fsPath(root.resolve('foo').resolve('bar'))).isDirectory()).to.be.true;
|
|
83
|
-
await sleep(
|
|
83
|
+
await sleep(2000);
|
|
84
84
|
|
|
85
85
|
fs.writeFileSync(FileUri.fsPath(root.resolve('foo').resolve('bar').resolve('baz.txt')), 'baz');
|
|
86
86
|
expect(fs.readFileSync(FileUri.fsPath(root.resolve('foo').resolve('bar').resolve('baz.txt')), 'utf8')).to.be.equal('baz');
|
|
87
|
-
await sleep(
|
|
87
|
+
await sleep(2000);
|
|
88
88
|
|
|
89
89
|
assert.deepStrictEqual([...actualUris], expectedUris);
|
|
90
90
|
});
|
|
@@ -119,7 +119,8 @@ describe('parcel-filesystem-watcher', function (): void {
|
|
|
119
119
|
assert.deepStrictEqual(actualUris.size, 0);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
// Skip on Mac: this test fails in Mac CI due to case-insensitive filesystem behavior
|
|
123
|
+
it.skip('Renaming should emit a DELETED and ADDED event', async function (): Promise<void> {
|
|
123
124
|
const file_txt = root.resolve('file.txt');
|
|
124
125
|
const FILE_txt = root.resolve('FILE.txt');
|
|
125
126
|
const changes: FileChange[] = [];
|