mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 11:15:24 +03:00
merge upstream
This commit is contained in:
commit
8dcdae902a
3
apm/package-lock.json
generated
3
apm/package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"node-gyp": "3.4.0",
|
||||
"npm": "6.2.0",
|
||||
"open": "0.0.5",
|
||||
"plist": "git+https://github.com/nathansobo/node-plist.git#bd3a93387f1d4b2cff819b200870d35465796e77",
|
||||
"q": "~0.9.7",
|
||||
"read": "~1.0.5",
|
||||
"request": "^2.87.0",
|
||||
@ -3973,7 +3974,7 @@
|
||||
},
|
||||
"plist": {
|
||||
"version": "git+https://github.com/nathansobo/node-plist.git#bd3a93387f1d4b2cff819b200870d35465796e77",
|
||||
"from": "git+https://github.com/nathansobo/node-plist.git#bd3a93387f1d4b2cff819b200870d35465796e77",
|
||||
"from": "git+https://github.com/nathansobo/node-plist.git",
|
||||
"requires": {
|
||||
"xmlbuilder": "0.4.x",
|
||||
"xmldom": "0.1.x"
|
||||
|
201
package-lock.json
generated
201
package-lock.json
generated
@ -5,15 +5,15 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@atom/nsfw": {
|
||||
"version": "1.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@atom/nsfw/-/nsfw-1.0.21.tgz",
|
||||
"integrity": "sha512-ERz76RZstjDvZPM4FHwgO+9dzGkki0mJsn2r+Mu4yn29MzUuAtSmtyJDPmupVUvmz2PF7KvRbC/W2A8A6BHQhA==",
|
||||
"version": "1.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@atom/nsfw/-/nsfw-1.0.18.tgz",
|
||||
"integrity": "sha512-YceKV9a3X62mh4Q78Nyi8aTRaoVGdpeJBHogL8gxU17iBhEpYvxGgMfTe02j1hH2taFT4barkZ5RdZkGKIsJ/w==",
|
||||
"requires": {
|
||||
"fs-extra": "^7.0.0",
|
||||
"fs-extra": "^0.26.5",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isundefined": "^3.0.1",
|
||||
"nan": "^2.10.0",
|
||||
"promisify-node": "^0.5.0"
|
||||
"nan": "^2.0.0",
|
||||
"promisify-node": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"@atom/source-map-support": {
|
||||
@ -306,9 +306,9 @@
|
||||
}
|
||||
},
|
||||
"atom-keymap": {
|
||||
"version": "8.2.12",
|
||||
"resolved": "https://registry.npmjs.org/atom-keymap/-/atom-keymap-8.2.12.tgz",
|
||||
"integrity": "sha512-qAXylPa/uysvYhZC1zJR7yG7QFs2RqU+2646JYGeiqgm0nMxLpdJpJ9ABIXbDNxzjhlRXNmZkr5V6N22RWjh4Q==",
|
||||
"version": "8.2.13",
|
||||
"resolved": "https://registry.npmjs.org/atom-keymap/-/atom-keymap-8.2.13.tgz",
|
||||
"integrity": "sha512-RNf+5KbAiXpNV2KZT0+XYpTRFE8rhq7NrBryghJAOlwayY3g3z6Kp9tMfaPJ05BkPo9mChcaFO6SKUL8LTQcBg==",
|
||||
"requires": {
|
||||
"clear-cut": "^2",
|
||||
"emissary": "^1.1.0",
|
||||
@ -1164,6 +1164,14 @@
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"cached-run-in-this-context": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cached-run-in-this-context/-/cached-run-in-this-context-0.5.0.tgz",
|
||||
"integrity": "sha512-FdtDP0u8WjetQ95nLz9vI06efJTFrmtmk5ZT6FECpyTKi9aakNLMHyMH21WRbGYyWlbmB/QlRoB/g1lcEpyjMw==",
|
||||
"requires": {
|
||||
"nan": "^2.10.0"
|
||||
}
|
||||
},
|
||||
"caller-callsite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||
@ -1303,6 +1311,11 @@
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
|
||||
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE="
|
||||
},
|
||||
"circular-json": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.7.tgz",
|
||||
"integrity": "sha512-/pXoV1JA847qRKPrHbBK6YIBGFF8GOP4wzSgUOA7q0ew0vAv0iJswP+2/nZQ9uzA3Azi7eTrg9L2yzXc/7ZMIA=="
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
|
||||
@ -1630,9 +1643,9 @@
|
||||
}
|
||||
},
|
||||
"date-format": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz",
|
||||
"integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA=="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
|
||||
"integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
@ -2219,8 +2232,8 @@
|
||||
}
|
||||
},
|
||||
"find-and-replace": {
|
||||
"version": "https://www.atom.io/api/packages/find-and-replace/versions/0.218.0/tarball",
|
||||
"integrity": "sha512-c77OpEcgce8cfPgjPQStEvK016AVmNMUfKIuCYgzYbjTik2lCSU+QAEG3q6MitIzqoFiSW6WW3FL502HFyZwKg==",
|
||||
"version": "https://www.atom.io/api/packages/find-and-replace/versions/0.218.9/tarball",
|
||||
"integrity": "sha512-j1KWRa8Ki9P3G3//kP9sWUqa8vva3+HiWfZJAR8kAaOJFUqy9EGOp6SR3Xs6ChQbk8kHmQBnIcYUjCz/gDoJ/g==",
|
||||
"requires": {
|
||||
"binary-search": "^1.3.3",
|
||||
"element-resize-detector": "^1.1.10",
|
||||
@ -2266,11 +2279,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"flatted": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
|
||||
"integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg=="
|
||||
},
|
||||
"flatten": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
|
||||
@ -2385,13 +2393,25 @@
|
||||
"integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz",
|
||||
"integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==",
|
||||
"version": "0.26.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
|
||||
"integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
"jsonfile": "^2.1.0",
|
||||
"klaw": "^1.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"rimraf": "^2.2.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonfile": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
|
||||
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
@ -2403,9 +2423,9 @@
|
||||
}
|
||||
},
|
||||
"fs-plus": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz",
|
||||
"integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.2.tgz",
|
||||
"integrity": "sha1-a19Sp3EolMTd6f2PgfqMYN8EHz0=",
|
||||
"requires": {
|
||||
"async": "^1.5.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
@ -2415,7 +2435,7 @@
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||
}
|
||||
}
|
||||
@ -2464,8 +2484,8 @@
|
||||
"integrity": "sha1-gy9kifvodnaUWVmckUpnDsIpR+4="
|
||||
},
|
||||
"fuzzy-finder": {
|
||||
"version": "https://www.atom.io/api/packages/fuzzy-finder/versions/1.9.0/tarball",
|
||||
"integrity": "sha512-YJTPPMZLQmDiUa6eoONhHF7sOvAPQqrXNFLfPGAUItDUoJxvJBwkPcxh+ryCHLlO8MuoJdDvAKHsU0mGzb/JeQ==",
|
||||
"version": "https://www.atom.io/api/packages/fuzzy-finder/versions/1.9.2/tarball",
|
||||
"integrity": "sha512-gpr1DXfxCEwMO48uCudocIZomhHlMJk+l1lAXJXwamtprDRsuzSqVMFjlyM49/cn4P5ToMhVHHHFngg0wklpvw==",
|
||||
"requires": {
|
||||
"async": "0.2.6",
|
||||
"atom-select-list": "^0.7.0",
|
||||
@ -3217,6 +3237,14 @@
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"klaw": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
|
||||
"integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.9"
|
||||
}
|
||||
},
|
||||
"language-c": {
|
||||
"version": "https://www.atom.io/api/packages/language-c/versions/0.60.14/tarball",
|
||||
"integrity": "sha512-jGFMc9vNutvBflFdoUXWJqxBFdMFG7n13PxS8z+SB8H0taZStq55JixDaQ0i/qu7ay4K5BCJ7y/PHdL13vjouQ==",
|
||||
@ -3661,21 +3689,21 @@
|
||||
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
|
||||
},
|
||||
"log4js": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-4.0.0.tgz",
|
||||
"integrity": "sha512-XlxZfcFAvQjnjCJBIV/EpsPmrVC12n+TxNUKgrmQh6eeA+9X+6UqvaRNV8t6dpMtXszl1LAQimB4pqyp2Gsgfw==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/log4js/-/log4js-3.0.5.tgz",
|
||||
"integrity": "sha512-IX5c3G/7fuTtdr0JjOT2OIR12aTESVhsH6cEsijloYwKgcPRlO6DgOU72v0UFhWcoV1HN6+M3dwT89qVPLXm0w==",
|
||||
"requires": {
|
||||
"date-format": "^2.0.0",
|
||||
"circular-json": "^0.5.5",
|
||||
"date-format": "^1.2.0",
|
||||
"debug": "^3.1.0",
|
||||
"flatted": "^2.0.0",
|
||||
"rfdc": "^1.1.2",
|
||||
"streamroller": "^1.0.1"
|
||||
"streamroller": "0.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
|
||||
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
@ -4262,9 +4290,9 @@
|
||||
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
|
||||
},
|
||||
"pathwatcher": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pathwatcher/-/pathwatcher-8.0.2.tgz",
|
||||
"integrity": "sha512-zuP+fLmB2IB6z89ikcehA+vG/ITx3Cmhaj3DJrBgnbdss6dwPolSq7cDBjgZ78Vl+SXmG7CHGIOM5mqdT9h7BQ==",
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pathwatcher/-/pathwatcher-8.0.1.tgz",
|
||||
"integrity": "sha1-UaLOKgHbbDLYZ/ZYXvKEvmvQo64=",
|
||||
"requires": {
|
||||
"async": "~0.2.10",
|
||||
"emissary": "^1.3.2",
|
||||
@ -4272,19 +4300,19 @@
|
||||
"fs-plus": "^3.0.0",
|
||||
"grim": "^2.0.1",
|
||||
"iconv-lite": "~0.4.4",
|
||||
"nan": "^2.10.0",
|
||||
"nan": "2.x",
|
||||
"underscore-plus": "~1.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.2.10",
|
||||
"resolved": "http://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||
"integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
|
||||
},
|
||||
"grim": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/grim/-/grim-2.0.2.tgz",
|
||||
"integrity": "sha512-Qj7hTJRfd87E/gUgfvM0YIH/g2UA2SV6niv6BYXk1o6w4mhgv+QyYM1EjOJQljvzgEj4SqSsRWldXIeKHz3e3Q==",
|
||||
"integrity": "sha1-52CinKe4NDsMH/r2ziDyGkbuiu0=",
|
||||
"requires": {
|
||||
"event-kit": "^2.0.0"
|
||||
}
|
||||
@ -4422,12 +4450,11 @@
|
||||
}
|
||||
},
|
||||
"promisify-node": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/promisify-node/-/promisify-node-0.5.0.tgz",
|
||||
"integrity": "sha512-GR2E4qgCoKFTprhULqP2OP3bl8kHo16XtnqtkHH6be7tPW1yL6rXd15nl3oV2sLTFv1+j6tqoF69VVpFtJ/j+A==",
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/promisify-node/-/promisify-node-0.3.0.tgz",
|
||||
"integrity": "sha1-tLVaz5D6p9K4uQyjlomQhsAwYM8=",
|
||||
"requires": {
|
||||
"nodegit-promise": "^4.0.0",
|
||||
"object-assign": "^4.1.1"
|
||||
"nodegit-promise": "~4.0.0"
|
||||
}
|
||||
},
|
||||
"prop-types": {
|
||||
@ -4976,8 +5003,8 @@
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
},
|
||||
"settings-view": {
|
||||
"version": "https://www.atom.io/api/packages/settings-view/versions/0.259.0/tarball",
|
||||
"integrity": "sha512-jNQuTozGf1uQtS4Y4hlUT15STnmnKlAWzzJOIe+ts2S/SdrGHZwxhNykJFzM8gC8j4N3Kjb8CkgmbCDzZ98aEw==",
|
||||
"version": "https://www.atom.io/api/packages/settings-view/versions/0.260.0/tarball",
|
||||
"integrity": "sha512-uZspfpknxC+47Zr9FJmkrAzR+a7yprv6hevZU6dUIQ1q2n0ws/Bxm6mgZVjyxpDmnNRwxiMrzCl2UArE05sNOg==",
|
||||
"requires": {
|
||||
"async": "~0.2.9",
|
||||
"dompurify": "^1.0.2",
|
||||
@ -5087,8 +5114,8 @@
|
||||
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
|
||||
},
|
||||
"snippets": {
|
||||
"version": "https://www.atom.io/api/packages/snippets/versions/1.4.0/tarball",
|
||||
"integrity": "sha512-pfw/YOwYeU4xJBEe/qztZx8Lq0ODLtSy7seoaMRu4w1lGlzl0HsqBeqTwLxR6bak3nS5WLj+k3hSJO+yNJWH2w==",
|
||||
"version": "https://www.atom.io/api/packages/snippets/versions/1.4.1/tarball",
|
||||
"integrity": "sha512-bLQmuMmyC+Sfjm/jdPbY3j/Vml3E4ApKhMFRb4AJLTde5m7TEhdTmsQBtkf+vFqGfr76tZ144F9NWH12ryS5rw==",
|
||||
"requires": {
|
||||
"async": "~0.2.6",
|
||||
"atom-select-list": "^0.7.0",
|
||||
@ -5161,22 +5188,22 @@
|
||||
"integrity": "sha1-enzShHDMbToc/m1miG9rxDDTrIc="
|
||||
},
|
||||
"spell-check": {
|
||||
"version": "https://www.atom.io/api/packages/spell-check/versions/0.74.3/tarball",
|
||||
"integrity": "sha512-QYy0xpSKp8OSZjlvq7fOjrf/NdG4BncHvn9AmO/vcuJvWAoiwEWhqqB+BSA6uSrxOxJeJhbDJSxOrvm4YXs1xQ==",
|
||||
"version": "https://www.atom.io/api/packages/spell-check/versions/0.74.2/tarball",
|
||||
"integrity": "sha512-WhwhDF4nznhQuwnRemZbXODI6aqax2HlHudfLEjbhzkRGjEbfssRK82lRRvCK9LmQh3fAqCqbmELU40NEODJ8Q==",
|
||||
"requires": {
|
||||
"atom-pathspec": "^0.0.0",
|
||||
"atom-select-list": "^0.7.0",
|
||||
"multi-integer-range": "^2.0.0",
|
||||
"natural": "^0.4.0",
|
||||
"spellchecker": "^3.5.1",
|
||||
"spellchecker": "^3.4.4",
|
||||
"spelling-manager": "^1.1.0",
|
||||
"underscore-plus": "^1"
|
||||
}
|
||||
},
|
||||
"spellchecker": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/spellchecker/-/spellchecker-3.5.1.tgz",
|
||||
"integrity": "sha512-R1qUBsDZzio+7MFZN6/AtPUe5NGvnc0wywckuXAlp9akASaYSFqKuI5O8p3rSiA+yKP31qC7Iijjoygmzkh6xw==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spellchecker/-/spellchecker-3.5.0.tgz",
|
||||
"integrity": "sha512-Xa7XnRulYhh5N/XENeL2O8/875XhLBjos7Bemv0rfcgV6ojNYMSrXscUZUGJwniX2t67eY+lNUJeptD1bMauHQ==",
|
||||
"requires": {
|
||||
"any-promise": "^1.3.0",
|
||||
"nan": "^2.10.0"
|
||||
@ -5275,38 +5302,24 @@
|
||||
}
|
||||
},
|
||||
"streamroller": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.1.tgz",
|
||||
"integrity": "sha512-FKL2mEB0A+XTIWSOlBHm2DvdsER5cGraqrUufO0lFMfsVY+Gpb3TC29Z+6/l0Urbb7vtm6m9zJOQBVl6fEkZBA==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz",
|
||||
"integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=",
|
||||
"requires": {
|
||||
"async": "^2.6.1",
|
||||
"date-format": "^2.0.0",
|
||||
"date-format": "^1.2.0",
|
||||
"debug": "^3.1.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"lodash": "^4.17.10"
|
||||
"mkdirp": "^0.5.1",
|
||||
"readable-stream": "^2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
|
||||
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
@ -5378,9 +5391,9 @@
|
||||
}
|
||||
},
|
||||
"superstring": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/superstring/-/superstring-2.3.6.tgz",
|
||||
"integrity": "sha512-kDTXCXArhHL1lRk2zBW7ByRJByqVwoLK3E3jlf8+LcwQLZgSMs9dwrDHDpBdoOm89kstSBSrGcW8OJqNkxjWrQ==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/superstring/-/superstring-2.3.4.tgz",
|
||||
"integrity": "sha512-DcNkTCdB9F3FMZRdURSALsHi+7DWqFCI0cH+Eg8mwBg+kxQs6GeB3LrGUvCI5bEB6Dtlu2ox8UYN0onPN4JeZQ==",
|
||||
"requires": {
|
||||
"nan": "^2.10.0"
|
||||
}
|
||||
@ -5500,21 +5513,21 @@
|
||||
}
|
||||
},
|
||||
"text-buffer": {
|
||||
"version": "13.15.3",
|
||||
"resolved": "https://registry.npmjs.org/text-buffer/-/text-buffer-13.15.3.tgz",
|
||||
"integrity": "sha512-H2fz/N15g0fBP7R33FUFLnIyND+Lji/xmuvHg9rKgmfCh7NAVxiFIvnZTabuBhL9InqPrtV5t4hkUy+r3dNXMg==",
|
||||
"version": "13.15.1",
|
||||
"resolved": "https://registry.npmjs.org/text-buffer/-/text-buffer-13.15.1.tgz",
|
||||
"integrity": "sha512-LZx7EKhVwjo50Y9ZF6WDP2S4zLUcHAagHZhn6HzHjr0SxyWtm95HG7ApSzgLGvIzCZEoTzMYt62scUdesfzYuw==",
|
||||
"requires": {
|
||||
"delegato": "^1.0.0",
|
||||
"diff": "^2.2.1",
|
||||
"emissary": "^1.0.0",
|
||||
"event-kit": "^2.4.0",
|
||||
"fs-admin": "^0.1.7",
|
||||
"fs-admin": "^0.1.4",
|
||||
"fs-plus": "^3.0.0",
|
||||
"grim": "^2.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"pathwatcher": "8.0.2",
|
||||
"pathwatcher": "8.0.1",
|
||||
"serializable": "^1.0.3",
|
||||
"superstring": "2.3.6",
|
||||
"superstring": "2.3.4",
|
||||
"underscore-plus": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -5957,8 +5970,8 @@
|
||||
"integrity": "sha1-BNoCcKh6d4VAFzzb8KLbSZqNnik="
|
||||
},
|
||||
"welcome": {
|
||||
"version": "https://www.atom.io/api/packages/welcome/versions/0.36.7/tarball",
|
||||
"integrity": "sha512-z1EOTRYfN23fBL75Shrbe/j2VDelw2c8oKRXC2MqLLBiWUCFDkxsEo1R7OfiCaNZi7q/0ue0fqLCpENHker4FA==",
|
||||
"version": "https://www.atom.io/api/packages/welcome/versions/0.36.8/tarball",
|
||||
"integrity": "sha512-U2EFFqgGxqQ5eMFFP3bl5oBSSvb0kx4VZwQVyO8oXqjuveiV2gUSrnDP68C9yCqJxcMR9ZP4ZYTLsf255F7CsA==",
|
||||
"requires": {
|
||||
"etch": "0.9.0"
|
||||
},
|
||||
|
37
package.json
37
package.json
@ -12,9 +12,9 @@
|
||||
"url": "https://github.com/atom/atom/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"electronVersion": "3.1.3",
|
||||
"electronVersion": "2.0.16",
|
||||
"dependencies": {
|
||||
"@atom/nsfw": "1.0.21",
|
||||
"@atom/nsfw": "1.0.18",
|
||||
"@atom/source-map-support": "^0.3.4",
|
||||
"@atom/watcher": "1.3.1",
|
||||
"about": "file:packages/about",
|
||||
@ -22,7 +22,7 @@
|
||||
"async": "0.2.6",
|
||||
"atom-dark-syntax": "file:packages/atom-dark-syntax",
|
||||
"atom-dark-ui": "file:packages/atom-dark-ui",
|
||||
"atom-keymap": "8.2.12",
|
||||
"atom-keymap": "8.2.13",
|
||||
"atom-light-syntax": "file:packages/atom-light-syntax",
|
||||
"atom-light-ui": "file:packages/atom-light-ui",
|
||||
"atom-select-list": "^0.7.2",
|
||||
@ -39,6 +39,7 @@
|
||||
"base16-tomorrow-light-theme": "file:packages/base16-tomorrow-light-theme",
|
||||
"bookmarks": "https://www.atom.io/api/packages/bookmarks/versions/0.46.0/tarball",
|
||||
"bracket-matcher": "https://www.atom.io/api/packages/bracket-matcher/versions/0.90.4/tarball",
|
||||
"cached-run-in-this-context": "0.5.0",
|
||||
"chai": "3.5.0",
|
||||
"chart.js": "^2.3.0",
|
||||
"clear-cut": "^2.0.2",
|
||||
@ -54,15 +55,15 @@
|
||||
"etch": "^0.12.6",
|
||||
"event-kit": "^2.5.3",
|
||||
"exception-reporting": "file:packages/exception-reporting",
|
||||
"find-and-replace": "https://www.atom.io/api/packages/find-and-replace/versions/0.218.0/tarball",
|
||||
"find-and-replace": "https://www.atom.io/api/packages/find-and-replace/versions/0.218.9/tarball",
|
||||
"find-parent-dir": "^0.3.0",
|
||||
"first-mate": "7.1.3",
|
||||
"focus-trap": "2.4.5",
|
||||
"fs-admin": "^0.1.7",
|
||||
"fs-plus": "^3.1.1",
|
||||
"fs-plus": "^3.0.1",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"fuzzy-finder": "https://www.atom.io/api/packages/fuzzy-finder/versions/1.9.0/tarball",
|
||||
"fuzzy-finder": "https://www.atom.io/api/packages/fuzzy-finder/versions/1.9.2/tarball",
|
||||
"git-diff": "file:packages/git-diff",
|
||||
"git-utils": "5.2.1",
|
||||
"github": "https://www.atom.io/api/packages/github/versions/0.26.0/tarball",
|
||||
@ -132,7 +133,7 @@
|
||||
"one-light-ui": "file:packages/one-light-ui",
|
||||
"open-on-github": "https://www.atom.io/api/packages/open-on-github/versions/1.3.1/tarball",
|
||||
"package-generator": "https://www.atom.io/api/packages/package-generator/versions/1.3.0/tarball",
|
||||
"pathwatcher": "8.0.2",
|
||||
"pathwatcher": "8.0.1",
|
||||
"postcss": "5.2.4",
|
||||
"postcss-selector-parser": "2.2.1",
|
||||
"property-accessors": "^1.1.3",
|
||||
@ -144,18 +145,18 @@
|
||||
"season": "^6.0.2",
|
||||
"semver": "^4.3.3",
|
||||
"service-hub": "^0.7.4",
|
||||
"settings-view": "https://www.atom.io/api/packages/settings-view/versions/0.259.0/tarball",
|
||||
"settings-view": "https://www.atom.io/api/packages/settings-view/versions/0.260.0/tarball",
|
||||
"sinon": "1.17.4",
|
||||
"snippets": "https://www.atom.io/api/packages/snippets/versions/1.4.0/tarball",
|
||||
"snippets": "https://www.atom.io/api/packages/snippets/versions/1.4.1/tarball",
|
||||
"solarized-dark-syntax": "file:packages/solarized-dark-syntax",
|
||||
"solarized-light-syntax": "file:packages/solarized-light-syntax",
|
||||
"spell-check": "https://www.atom.io/api/packages/spell-check/versions/0.74.3/tarball",
|
||||
"spell-check": "https://www.atom.io/api/packages/spell-check/versions/0.74.2/tarball",
|
||||
"status-bar": "https://www.atom.io/api/packages/status-bar/versions/1.8.17/tarball",
|
||||
"styleguide": "https://www.atom.io/api/packages/styleguide/versions/0.49.12/tarball",
|
||||
"symbols-view": "https://www.atom.io/api/packages/symbols-view/versions/0.118.2/tarball",
|
||||
"tabs": "https://www.atom.io/api/packages/tabs/versions/0.110.0/tarball",
|
||||
"temp": "^0.8.3",
|
||||
"text-buffer": "13.15.3",
|
||||
"text-buffer": "13.15.1",
|
||||
"timecop": "https://www.atom.io/api/packages/timecop/versions/0.36.2/tarball",
|
||||
"tree-sitter": "0.13.23",
|
||||
"tree-sitter-css": "^0.13.7",
|
||||
@ -163,7 +164,7 @@
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.8",
|
||||
"update-package-dependencies": "https://www.atom.io/api/packages/update-package-dependencies/versions/0.13.1/tarball",
|
||||
"welcome": "https://www.atom.io/api/packages/welcome/versions/0.36.7/tarball",
|
||||
"welcome": "https://www.atom.io/api/packages/welcome/versions/0.36.8/tarball",
|
||||
"whitespace": "https://www.atom.io/api/packages/whitespace/versions/0.37.7/tarball",
|
||||
"winreg": "^1.2.1",
|
||||
"wrap-guide": "https://www.atom.io/api/packages/wrap-guide/versions/0.41.0/tarball",
|
||||
@ -200,8 +201,8 @@
|
||||
"dev-live-reload": "file:./packages/dev-live-reload",
|
||||
"encoding-selector": "0.23.9",
|
||||
"exception-reporting": "file:./packages/exception-reporting",
|
||||
"find-and-replace": "0.218.0",
|
||||
"fuzzy-finder": "1.9.0",
|
||||
"find-and-replace": "0.218.9",
|
||||
"fuzzy-finder": "1.9.2",
|
||||
"github": "0.26.0",
|
||||
"git-diff": "file:./packages/git-diff",
|
||||
"go-to-line": "file:./packages/go-to-line",
|
||||
@ -216,9 +217,9 @@
|
||||
"notifications": "0.70.6",
|
||||
"open-on-github": "1.3.1",
|
||||
"package-generator": "1.3.0",
|
||||
"settings-view": "0.259.0",
|
||||
"snippets": "1.4.0",
|
||||
"spell-check": "0.74.3",
|
||||
"settings-view": "0.260.0",
|
||||
"snippets": "1.4.1",
|
||||
"spell-check": "0.74.2",
|
||||
"status-bar": "1.8.17",
|
||||
"styleguide": "0.49.12",
|
||||
"symbols-view": "0.118.2",
|
||||
@ -226,7 +227,7 @@
|
||||
"timecop": "0.36.2",
|
||||
"tree-view": "0.224.5",
|
||||
"update-package-dependencies": "0.13.1",
|
||||
"welcome": "0.36.7",
|
||||
"welcome": "0.36.8",
|
||||
"whitespace": "0.37.7",
|
||||
"wrap-guide": "0.41.0",
|
||||
"language-c": "0.60.14",
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {CompositeDisposable, Emitter} = require('atom')
|
||||
const { CompositeDisposable, Emitter } = require('atom')
|
||||
const AboutView = require('./components/about-view')
|
||||
|
||||
// Deferred requires
|
||||
@ -14,16 +14,22 @@ module.exports = class About {
|
||||
aboutView: null
|
||||
}
|
||||
|
||||
this.subscriptions.add(atom.workspace.addOpener((uriToOpen) => {
|
||||
if (uriToOpen === this.state.uri) {
|
||||
return this.deserialize()
|
||||
}
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
atom.workspace.addOpener(uriToOpen => {
|
||||
if (uriToOpen === this.state.uri) {
|
||||
return this.deserialize()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
this.subscriptions.add(atom.commands.add('atom-workspace', 'about:view-release-notes', () => {
|
||||
shell = shell || require('electron').shell
|
||||
shell.openExternal(this.state.updateManager.getReleaseNotesURLForCurrentVersion())
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
atom.commands.add('atom-workspace', 'about:view-release-notes', () => {
|
||||
shell = shell || require('electron').shell
|
||||
shell.openExternal(
|
||||
this.state.updateManager.getReleaseNotesURLForCurrentVersion()
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
@ -31,14 +37,14 @@ module.exports = class About {
|
||||
this.views.aboutView = null
|
||||
|
||||
if (this.state.updateManager) this.state.updateManager.dispose()
|
||||
this.setState({updateManager: null})
|
||||
this.setState({ updateManager: null })
|
||||
|
||||
this.subscriptions.dispose()
|
||||
}
|
||||
|
||||
setState (newState) {
|
||||
if (newState && typeof newState === 'object') {
|
||||
let {state} = this
|
||||
let { state } = this
|
||||
this.state = Object.assign({}, state, newState)
|
||||
|
||||
this.didChange()
|
||||
|
@ -1,16 +1,20 @@
|
||||
const {CompositeDisposable} = require('atom')
|
||||
const { CompositeDisposable } = require('atom')
|
||||
const etch = require('etch')
|
||||
const EtchComponent = require('../etch-component')
|
||||
|
||||
const $ = etch.dom
|
||||
|
||||
module.exports =
|
||||
class AboutStatusBar extends EtchComponent {
|
||||
module.exports = class AboutStatusBar extends EtchComponent {
|
||||
constructor () {
|
||||
super()
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
|
||||
this.subscriptions.add(atom.tooltips.add(this.element, {title: 'An update will be installed the next time Atom is relaunched.<br/><br/>Click the squirrel icon for more information.'}))
|
||||
this.subscriptions.add(
|
||||
atom.tooltips.add(this.element, {
|
||||
title:
|
||||
'An update will be installed the next time Atom is relaunched.<br/><br/>Click the squirrel icon for more information.'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
handleClick () {
|
||||
@ -18,8 +22,12 @@ class AboutStatusBar extends EtchComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
return $.div({className: 'about-release-notes inline-block', onclick: this.handleClick.bind(this)},
|
||||
$.span({type: 'button', className: 'icon icon-squirrel'})
|
||||
return $.div(
|
||||
{
|
||||
className: 'about-release-notes inline-block',
|
||||
onclick: this.handleClick.bind(this)
|
||||
},
|
||||
$.span({ type: 'button', className: 'icon icon-squirrel' })
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {Disposable} = require('atom')
|
||||
const { Disposable } = require('atom')
|
||||
const etch = require('etch')
|
||||
const shell = require('shell')
|
||||
const AtomLogo = require('./atom-logo')
|
||||
@ -7,8 +7,7 @@ const UpdateView = require('./update-view')
|
||||
|
||||
const $ = etch.dom
|
||||
|
||||
module.exports =
|
||||
class AboutView extends EtchComponent {
|
||||
module.exports = class AboutView extends EtchComponent {
|
||||
handleAtomVersionClick (e) {
|
||||
e.preventDefault()
|
||||
atom.clipboard.write(this.props.currentAtomVersion)
|
||||
@ -31,12 +30,17 @@ class AboutView extends EtchComponent {
|
||||
|
||||
handleReleaseNotesClick (e) {
|
||||
e.preventDefault()
|
||||
shell.openExternal(this.props.updateManager.getReleaseNotesURLForAvailableVersion())
|
||||
shell.openExternal(
|
||||
this.props.updateManager.getReleaseNotesURLForAvailableVersion()
|
||||
)
|
||||
}
|
||||
|
||||
handleLicenseClick (e) {
|
||||
e.preventDefault()
|
||||
atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-license')
|
||||
atom.commands.dispatch(
|
||||
atom.views.getView(atom.workspace),
|
||||
'application:open-license'
|
||||
)
|
||||
}
|
||||
|
||||
handleTermsOfUseClick (e) {
|
||||
@ -46,7 +50,9 @@ class AboutView extends EtchComponent {
|
||||
|
||||
handleHowToUpdateClick (e) {
|
||||
e.preventDefault()
|
||||
shell.openExternal('https://flight-manual.atom.io/getting-started/sections/installing-atom/')
|
||||
shell.openExternal(
|
||||
'https://flight-manual.atom.io/getting-started/sections/installing-atom/'
|
||||
)
|
||||
}
|
||||
|
||||
handleShowMoreClick (e) {
|
||||
@ -66,39 +72,87 @@ class AboutView extends EtchComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
return $.div({className: 'pane-item native-key-bindings about'},
|
||||
$.div({className: 'about-container'},
|
||||
$.header({className: 'about-header'},
|
||||
$.a({className: 'about-atom-io', href: 'https://atom.io'},
|
||||
return $.div(
|
||||
{ className: 'pane-item native-key-bindings about' },
|
||||
$.div(
|
||||
{ className: 'about-container' },
|
||||
$.header(
|
||||
{ className: 'about-header' },
|
||||
$.a(
|
||||
{ className: 'about-atom-io', href: 'https://atom.io' },
|
||||
$(AtomLogo)
|
||||
),
|
||||
$.div({className: 'about-header-info'},
|
||||
$.span({className: 'about-version-container inline-block atom', onclick: this.handleAtomVersionClick.bind(this)},
|
||||
$.span({className: 'about-version'}, `${this.props.currentAtomVersion} ${process.arch}`),
|
||||
$.span({className: 'icon icon-clippy about-copy-version'})
|
||||
$.div(
|
||||
{ className: 'about-header-info' },
|
||||
$.span(
|
||||
{
|
||||
className: 'about-version-container inline-block atom',
|
||||
onclick: this.handleAtomVersionClick.bind(this)
|
||||
},
|
||||
$.span(
|
||||
{ className: 'about-version' },
|
||||
`${this.props.currentAtomVersion} ${process.arch}`
|
||||
),
|
||||
$.span({ className: 'icon icon-clippy about-copy-version' })
|
||||
),
|
||||
$.a({className: 'about-header-release-notes', onclick: this.handleReleaseNotesClick.bind(this)}, 'Release Notes')
|
||||
$.a(
|
||||
{
|
||||
className: 'about-header-release-notes',
|
||||
onclick: this.handleReleaseNotesClick.bind(this)
|
||||
},
|
||||
'Release Notes'
|
||||
)
|
||||
),
|
||||
$.span({className: 'about-version-container inline-block show-more-expand', onclick: this.handleShowMoreClick.bind(this)},
|
||||
$.span({className: 'about-more-expand'}, 'Show more')
|
||||
$.span(
|
||||
{
|
||||
className:
|
||||
'about-version-container inline-block show-more-expand',
|
||||
onclick: this.handleShowMoreClick.bind(this)
|
||||
},
|
||||
$.span({ className: 'about-more-expand' }, 'Show more')
|
||||
),
|
||||
$.div({className: 'show-more hidden about-more-info'},
|
||||
$.div({className: 'about-more-info'},
|
||||
$.span({className: 'about-version-container inline-block electron', onclick: this.handleElectronVersionClick.bind(this)},
|
||||
$.span({className: 'about-more-version'}, `Electron: ${this.props.currentElectronVersion} `),
|
||||
$.span({className: 'icon icon-clippy about-copy-version'})
|
||||
$.div(
|
||||
{ className: 'show-more hidden about-more-info' },
|
||||
$.div(
|
||||
{ className: 'about-more-info' },
|
||||
$.span(
|
||||
{
|
||||
className: 'about-version-container inline-block electron',
|
||||
onclick: this.handleElectronVersionClick.bind(this)
|
||||
},
|
||||
$.span(
|
||||
{ className: 'about-more-version' },
|
||||
`Electron: ${this.props.currentElectronVersion} `
|
||||
),
|
||||
$.span({ className: 'icon icon-clippy about-copy-version' })
|
||||
)
|
||||
),
|
||||
$.div({className: 'about-more-info'},
|
||||
$.span({className: 'about-version-container inline-block chrome', onclick: this.handleChromeVersionClick.bind(this)},
|
||||
$.span({className: 'about-more-version'}, `Chrome: ${this.props.currentChromeVersion} `),
|
||||
$.span({className: 'icon icon-clippy about-copy-version'})
|
||||
$.div(
|
||||
{ className: 'about-more-info' },
|
||||
$.span(
|
||||
{
|
||||
className: 'about-version-container inline-block chrome',
|
||||
onclick: this.handleChromeVersionClick.bind(this)
|
||||
},
|
||||
$.span(
|
||||
{ className: 'about-more-version' },
|
||||
`Chrome: ${this.props.currentChromeVersion} `
|
||||
),
|
||||
$.span({ className: 'icon icon-clippy about-copy-version' })
|
||||
)
|
||||
),
|
||||
$.div({className: 'about-more-info'},
|
||||
$.span({className: 'about-version-container inline-block node', onclick: this.handleNodeVersionClick.bind(this)},
|
||||
$.span({className: 'about-more-version'}, `Node: ${this.props.currentNodeVersion} `),
|
||||
$.span({className: 'icon icon-clippy about-copy-version'})
|
||||
$.div(
|
||||
{ className: 'about-more-info' },
|
||||
$.span(
|
||||
{
|
||||
className: 'about-version-container inline-block node',
|
||||
onclick: this.handleNodeVersionClick.bind(this)
|
||||
},
|
||||
$.span(
|
||||
{ className: 'about-more-version' },
|
||||
`Node: ${this.props.currentNodeVersion} `
|
||||
),
|
||||
$.span({ className: 'icon icon-clippy about-copy-version' })
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -112,24 +166,43 @@ class AboutView extends EtchComponent {
|
||||
viewUpdateInstructions: this.handleHowToUpdateClick.bind(this)
|
||||
}),
|
||||
|
||||
$.div({className: 'about-actions group-item'},
|
||||
$.div({className: 'btn-group'},
|
||||
$.button({className: 'btn view-license', onclick: this.handleLicenseClick.bind(this)}, 'License'),
|
||||
$.button({className: 'btn terms-of-use', onclick: this.handleTermsOfUseClick.bind(this)}, 'Terms of Use')
|
||||
$.div(
|
||||
{ className: 'about-actions group-item' },
|
||||
$.div(
|
||||
{ className: 'btn-group' },
|
||||
$.button(
|
||||
{
|
||||
className: 'btn view-license',
|
||||
onclick: this.handleLicenseClick.bind(this)
|
||||
},
|
||||
'License'
|
||||
),
|
||||
$.button(
|
||||
{
|
||||
className: 'btn terms-of-use',
|
||||
onclick: this.handleTermsOfUseClick.bind(this)
|
||||
},
|
||||
'Terms of Use'
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
$.div({className: 'about-love group-start'},
|
||||
$.span({className: 'icon icon-code'}),
|
||||
$.span({className: 'inline'}, ' with '),
|
||||
$.span({className: 'icon icon-heart'}),
|
||||
$.span({className: 'inline'}, ' by '),
|
||||
$.a({className: 'icon icon-logo-github', href: 'https://github.com'})
|
||||
$.div(
|
||||
{ className: 'about-love group-start' },
|
||||
$.span({ className: 'icon icon-code' }),
|
||||
$.span({ className: 'inline' }, ' with '),
|
||||
$.span({ className: 'icon icon-heart' }),
|
||||
$.span({ className: 'inline' }, ' by '),
|
||||
$.a({ className: 'icon icon-logo-github', href: 'https://github.com' })
|
||||
),
|
||||
|
||||
$.div({className: 'about-credits group-item'},
|
||||
$.span({className: 'inline'}, 'And the awesome '),
|
||||
$.a({href: 'https://github.com/atom/atom/contributors'}, 'Atom Community')
|
||||
$.div(
|
||||
{ className: 'about-credits group-item' },
|
||||
$.span({ className: 'inline' }, 'And the awesome '),
|
||||
$.a(
|
||||
{ href: 'https://github.com/atom/atom/contributors' },
|
||||
'Atom Community'
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -3,23 +3,74 @@ const EtchComponent = require('../etch-component')
|
||||
|
||||
const $ = etch.dom
|
||||
|
||||
module.exports =
|
||||
class AtomLogo extends EtchComponent {
|
||||
module.exports = class AtomLogo extends EtchComponent {
|
||||
render () {
|
||||
return $.svg({className: 'about-logo', width: '330px', height: '68px', viewBox: '0 0 330 68'},
|
||||
$.g({stroke: 'none', 'stroke-width': '1', fill: 'none', 'fill-rule': 'evenodd'},
|
||||
$.g({transform: 'translate(2.000000, 1.000000)'},
|
||||
$.g({transform: 'translate(96.000000, 8.000000)', fill: 'currentColor'},
|
||||
$.path({d: 'M185.498,3.399 C185.498,2.417 186.34,1.573 187.324,1.573 L187.674,1.573 C188.447,1.573 189.01,1.995 189.5,2.628 L208.676,30.862 L227.852,2.628 C228.272,1.995 228.905,1.573 229.676,1.573 L230.028,1.573 C231.01,1.573 231.854,2.417 231.854,3.399 L231.854,49.403 C231.854,50.387 231.01,51.231 230.028,51.231 C229.044,51.231 228.202,50.387 228.202,49.403 L228.202,8.246 L210.151,34.515 C209.729,35.148 209.237,35.428 208.606,35.428 C207.973,35.428 207.481,35.148 207.061,34.515 L189.01,8.246 L189.01,49.475 C189.01,50.457 188.237,51.231 187.254,51.231 C186.27,51.231 185.498,50.458 185.498,49.475 L185.498,3.399 L185.498,3.399 Z'}),
|
||||
$.path({d: 'M113.086,26.507 L113.086,26.367 C113.086,12.952 122.99,0.941 137.881,0.941 C152.77,0.941 162.533,12.811 162.533,26.225 L162.533,26.367 C162.533,39.782 152.629,51.792 137.74,51.792 C122.85,51.792 113.086,39.923 113.086,26.507 M158.74,26.507 L158.74,26.367 C158.74,14.216 149.89,4.242 137.74,4.242 C125.588,4.242 116.879,14.075 116.879,26.225 L116.879,26.367 C116.879,38.518 125.729,48.491 137.881,48.491 C150.031,48.491 158.74,38.658 158.74,26.507'}),
|
||||
$.path({d: 'M76.705,5.155 L60.972,5.155 C60.06,5.155 59.287,4.384 59.287,3.469 C59.287,2.556 60.059,1.783 60.972,1.783 L96.092,1.783 C97.004,1.783 97.778,2.555 97.778,3.469 C97.778,4.383 97.005,5.155 96.092,5.155 L80.358,5.155 L80.358,49.405 C80.358,50.387 79.516,51.231 78.532,51.231 C77.55,51.231 76.706,50.387 76.706,49.405 L76.706,5.155 L76.705,5.155 Z'}),
|
||||
$.path({d: 'M0.291,48.562 L21.291,3.05 C21.783,1.995 22.485,1.292 23.75,1.292 L23.891,1.292 C25.155,1.292 25.858,1.995 26.348,3.05 L47.279,48.421 C47.49,48.843 47.56,49.194 47.56,49.546 C47.56,50.458 46.788,51.231 45.803,51.231 C44.961,51.231 44.329,50.599 43.978,49.826 L38.219,37.183 L9.21,37.183 L3.45,49.897 C3.099,50.739 2.538,51.231 1.694,51.231 C0.781,51.231 0.008,50.529 0.008,49.685 C0.009,49.404 0.08,48.983 0.291,48.562 L0.291,48.562 Z M36.673,33.882 L23.749,5.437 L10.755,33.882 L36.673,33.882 L36.673,33.882 Z'})
|
||||
return $.svg(
|
||||
{
|
||||
className: 'about-logo',
|
||||
width: '330px',
|
||||
height: '68px',
|
||||
viewBox: '0 0 330 68'
|
||||
},
|
||||
$.g(
|
||||
{
|
||||
stroke: 'none',
|
||||
'stroke-width': '1',
|
||||
fill: 'none',
|
||||
'fill-rule': 'evenodd'
|
||||
},
|
||||
$.g(
|
||||
{ transform: 'translate(2.000000, 1.000000)' },
|
||||
$.g(
|
||||
{
|
||||
transform: 'translate(96.000000, 8.000000)',
|
||||
fill: 'currentColor'
|
||||
},
|
||||
$.path({
|
||||
d:
|
||||
'M185.498,3.399 C185.498,2.417 186.34,1.573 187.324,1.573 L187.674,1.573 C188.447,1.573 189.01,1.995 189.5,2.628 L208.676,30.862 L227.852,2.628 C228.272,1.995 228.905,1.573 229.676,1.573 L230.028,1.573 C231.01,1.573 231.854,2.417 231.854,3.399 L231.854,49.403 C231.854,50.387 231.01,51.231 230.028,51.231 C229.044,51.231 228.202,50.387 228.202,49.403 L228.202,8.246 L210.151,34.515 C209.729,35.148 209.237,35.428 208.606,35.428 C207.973,35.428 207.481,35.148 207.061,34.515 L189.01,8.246 L189.01,49.475 C189.01,50.457 188.237,51.231 187.254,51.231 C186.27,51.231 185.498,50.458 185.498,49.475 L185.498,3.399 L185.498,3.399 Z'
|
||||
}),
|
||||
$.path({
|
||||
d:
|
||||
'M113.086,26.507 L113.086,26.367 C113.086,12.952 122.99,0.941 137.881,0.941 C152.77,0.941 162.533,12.811 162.533,26.225 L162.533,26.367 C162.533,39.782 152.629,51.792 137.74,51.792 C122.85,51.792 113.086,39.923 113.086,26.507 M158.74,26.507 L158.74,26.367 C158.74,14.216 149.89,4.242 137.74,4.242 C125.588,4.242 116.879,14.075 116.879,26.225 L116.879,26.367 C116.879,38.518 125.729,48.491 137.881,48.491 C150.031,48.491 158.74,38.658 158.74,26.507'
|
||||
}),
|
||||
$.path({
|
||||
d:
|
||||
'M76.705,5.155 L60.972,5.155 C60.06,5.155 59.287,4.384 59.287,3.469 C59.287,2.556 60.059,1.783 60.972,1.783 L96.092,1.783 C97.004,1.783 97.778,2.555 97.778,3.469 C97.778,4.383 97.005,5.155 96.092,5.155 L80.358,5.155 L80.358,49.405 C80.358,50.387 79.516,51.231 78.532,51.231 C77.55,51.231 76.706,50.387 76.706,49.405 L76.706,5.155 L76.705,5.155 Z'
|
||||
}),
|
||||
$.path({
|
||||
d:
|
||||
'M0.291,48.562 L21.291,3.05 C21.783,1.995 22.485,1.292 23.75,1.292 L23.891,1.292 C25.155,1.292 25.858,1.995 26.348,3.05 L47.279,48.421 C47.49,48.843 47.56,49.194 47.56,49.546 C47.56,50.458 46.788,51.231 45.803,51.231 C44.961,51.231 44.329,50.599 43.978,49.826 L38.219,37.183 L9.21,37.183 L3.45,49.897 C3.099,50.739 2.538,51.231 1.694,51.231 C0.781,51.231 0.008,50.529 0.008,49.685 C0.009,49.404 0.08,48.983 0.291,48.562 L0.291,48.562 Z M36.673,33.882 L23.749,5.437 L10.755,33.882 L36.673,33.882 L36.673,33.882 Z'
|
||||
})
|
||||
),
|
||||
$.g({},
|
||||
$.path({d: 'M40.363,32.075 C40.874,34.44 39.371,36.77 37.006,37.282 C34.641,37.793 32.311,36.29 31.799,33.925 C31.289,31.56 32.791,29.23 35.156,28.718 C37.521,28.207 39.851,29.71 40.363,32.075', fill: 'currentColor'}),
|
||||
$.path({d: 'M48.578,28.615 C56.851,45.587 58.558,61.581 52.288,64.778 C45.822,68.076 33.326,56.521 24.375,38.969 C15.424,21.418 13.409,4.518 19.874,1.221 C22.689,-0.216 26.648,1.166 30.959,4.629', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'}),
|
||||
$.path({d: 'M7.64,39.45 C2.806,36.94 -0.009,33.915 0.154,30.79 C0.531,23.542 16.787,18.497 36.462,19.52 C56.137,20.544 71.781,27.249 71.404,34.497 C71.241,37.622 68.127,40.338 63.06,42.333', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'}),
|
||||
$.path({d: 'M28.828,59.354 C23.545,63.168 18.843,64.561 15.902,62.653 C9.814,58.702 13.572,42.102 24.296,25.575 C35.02,9.048 48.649,-1.149 54.736,2.803 C57.566,4.639 58.269,9.208 57.133,15.232', stroke: 'currentColor', 'stroke-width': '3.08', 'stroke-linecap': 'round'})
|
||||
$.g(
|
||||
{},
|
||||
$.path({
|
||||
d:
|
||||
'M40.363,32.075 C40.874,34.44 39.371,36.77 37.006,37.282 C34.641,37.793 32.311,36.29 31.799,33.925 C31.289,31.56 32.791,29.23 35.156,28.718 C37.521,28.207 39.851,29.71 40.363,32.075',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
$.path({
|
||||
d:
|
||||
'M48.578,28.615 C56.851,45.587 58.558,61.581 52.288,64.778 C45.822,68.076 33.326,56.521 24.375,38.969 C15.424,21.418 13.409,4.518 19.874,1.221 C22.689,-0.216 26.648,1.166 30.959,4.629',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '3.08',
|
||||
'stroke-linecap': 'round'
|
||||
}),
|
||||
$.path({
|
||||
d:
|
||||
'M7.64,39.45 C2.806,36.94 -0.009,33.915 0.154,30.79 C0.531,23.542 16.787,18.497 36.462,19.52 C56.137,20.544 71.781,27.249 71.404,34.497 C71.241,37.622 68.127,40.338 63.06,42.333',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '3.08',
|
||||
'stroke-linecap': 'round'
|
||||
}),
|
||||
$.path({
|
||||
d:
|
||||
'M28.828,59.354 C23.545,63.168 18.843,64.561 15.902,62.653 C9.814,58.702 13.572,42.102 24.296,25.575 C35.02,9.048 48.649,-1.149 54.736,2.803 C57.566,4.639 58.269,9.208 57.133,15.232',
|
||||
stroke: 'currentColor',
|
||||
'stroke-width': '3.08',
|
||||
'stroke-linecap': 'round'
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -4,12 +4,14 @@ const UpdateManager = require('../update-manager')
|
||||
|
||||
const $ = etch.dom
|
||||
|
||||
module.exports =
|
||||
class UpdateView extends EtchComponent {
|
||||
module.exports = class UpdateView extends EtchComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
if (this.props.updateManager.getAutoUpdatesEnabled() && this.props.updateManager.getState() === UpdateManager.State.Idle) {
|
||||
if (
|
||||
this.props.updateManager.getAutoUpdatesEnabled() &&
|
||||
this.props.updateManager.getState() === UpdateManager.State.Idle
|
||||
) {
|
||||
this.props.updateManager.checkForUpdate()
|
||||
}
|
||||
}
|
||||
@ -19,12 +21,18 @@ class UpdateView extends EtchComponent {
|
||||
}
|
||||
|
||||
shouldUpdateActionButtonBeDisabled () {
|
||||
let {state} = this.props.updateManager
|
||||
return state === UpdateManager.State.CheckingForUpdate || state === UpdateManager.State.DownloadingUpdate
|
||||
let { state } = this.props.updateManager
|
||||
return (
|
||||
state === UpdateManager.State.CheckingForUpdate ||
|
||||
state === UpdateManager.State.DownloadingUpdate
|
||||
)
|
||||
}
|
||||
|
||||
executeUpdateAction () {
|
||||
if (this.props.updateManager.state === UpdateManager.State.UpdateAvailableToInstall) {
|
||||
if (
|
||||
this.props.updateManager.state ===
|
||||
UpdateManager.State.UpdateAvailableToInstall
|
||||
) {
|
||||
this.props.updateManager.restartAndInstallUpdate()
|
||||
} else {
|
||||
this.props.updateManager.checkForUpdate()
|
||||
@ -36,44 +44,86 @@ class UpdateView extends EtchComponent {
|
||||
|
||||
switch (this.props.updateManager.state) {
|
||||
case UpdateManager.State.Idle:
|
||||
updateStatus = $.div({className: 'about-updates-item is-shown about-default-update-message'},
|
||||
this.props.updateManager.getAutoUpdatesEnabled() ? 'Atom will check for updates automatically' : 'Automatic updates are disabled please check manually'
|
||||
updateStatus = $.div(
|
||||
{
|
||||
className:
|
||||
'about-updates-item is-shown about-default-update-message'
|
||||
},
|
||||
this.props.updateManager.getAutoUpdatesEnabled()
|
||||
? 'Atom will check for updates automatically'
|
||||
: 'Automatic updates are disabled please check manually'
|
||||
)
|
||||
break
|
||||
case UpdateManager.State.CheckingForUpdate:
|
||||
updateStatus = $.div({className: 'about-updates-item app-checking-for-updates'},
|
||||
$.span({className: 'about-updates-label icon icon-search'}, 'Checking for updates...')
|
||||
updateStatus = $.div(
|
||||
{ className: 'about-updates-item app-checking-for-updates' },
|
||||
$.span(
|
||||
{ className: 'about-updates-label icon icon-search' },
|
||||
'Checking for updates...'
|
||||
)
|
||||
)
|
||||
break
|
||||
case UpdateManager.State.DownloadingUpdate:
|
||||
updateStatus = $.div({className: 'about-updates-item app-downloading-update'},
|
||||
$.span({className: 'loading loading-spinner-tiny inline-block'}),
|
||||
$.span({className: 'about-updates-label'}, 'Downloading update')
|
||||
updateStatus = $.div(
|
||||
{ className: 'about-updates-item app-downloading-update' },
|
||||
$.span({ className: 'loading loading-spinner-tiny inline-block' }),
|
||||
$.span({ className: 'about-updates-label' }, 'Downloading update')
|
||||
)
|
||||
break
|
||||
case UpdateManager.State.UpdateAvailableToInstall:
|
||||
updateStatus = $.div({className: 'about-updates-item app-update-available-to-install'},
|
||||
$.span({className: 'about-updates-label icon icon-squirrel'}, 'New update'),
|
||||
$.span({className: 'about-updates-version'}, this.props.availableVersion),
|
||||
$.a({className: 'about-updates-release-notes', onclick: this.props.viewUpdateReleaseNotes}, 'Release Notes')
|
||||
updateStatus = $.div(
|
||||
{ className: 'about-updates-item app-update-available-to-install' },
|
||||
$.span(
|
||||
{ className: 'about-updates-label icon icon-squirrel' },
|
||||
'New update'
|
||||
),
|
||||
$.span(
|
||||
{ className: 'about-updates-version' },
|
||||
this.props.availableVersion
|
||||
),
|
||||
$.a(
|
||||
{
|
||||
className: 'about-updates-release-notes',
|
||||
onclick: this.props.viewUpdateReleaseNotes
|
||||
},
|
||||
'Release Notes'
|
||||
)
|
||||
)
|
||||
break
|
||||
case UpdateManager.State.UpToDate:
|
||||
updateStatus = $.div({className: 'about-updates-item app-up-to-date'},
|
||||
$.span({className: 'icon icon-check'}),
|
||||
$.span({className: 'about-updates-label is-strong'}, 'Atom is up to date!')
|
||||
updateStatus = $.div(
|
||||
{ className: 'about-updates-item app-up-to-date' },
|
||||
$.span({ className: 'icon icon-check' }),
|
||||
$.span(
|
||||
{ className: 'about-updates-label is-strong' },
|
||||
'Atom is up to date!'
|
||||
)
|
||||
)
|
||||
break
|
||||
case UpdateManager.State.Unsupported:
|
||||
updateStatus = $.div({className: 'about-updates-item app-unsupported'},
|
||||
$.span({className: 'about-updates-label is-strong'}, 'Your system does not support automatic updates'),
|
||||
$.a({className: 'about-updates-instructions', onclick: this.props.viewUpdateInstructions}, 'How to update')
|
||||
updateStatus = $.div(
|
||||
{ className: 'about-updates-item app-unsupported' },
|
||||
$.span(
|
||||
{ className: 'about-updates-label is-strong' },
|
||||
'Your system does not support automatic updates'
|
||||
),
|
||||
$.a(
|
||||
{
|
||||
className: 'about-updates-instructions',
|
||||
onclick: this.props.viewUpdateInstructions
|
||||
},
|
||||
'How to update'
|
||||
)
|
||||
)
|
||||
break
|
||||
case UpdateManager.State.Error:
|
||||
updateStatus = $.div({className: 'about-updates-item app-update-error'},
|
||||
$.span({className: 'icon icon-x'}),
|
||||
$.span({className: 'about-updates-label app-error-message is-strong'}, this.props.updateManager.getErrorMessage())
|
||||
updateStatus = $.div(
|
||||
{ className: 'about-updates-item app-update-error' },
|
||||
$.span({ className: 'icon icon-x' }),
|
||||
$.span(
|
||||
{ className: 'about-updates-label app-error-message is-strong' },
|
||||
this.props.updateManager.getErrorMessage()
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
@ -82,37 +132,47 @@ class UpdateView extends EtchComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
return $.div({className: 'about-updates group-start'},
|
||||
$.div({className: 'about-updates-box'},
|
||||
$.div({className: 'about-updates-status'}, this.renderUpdateStatus()),
|
||||
return $.div(
|
||||
{ className: 'about-updates group-start' },
|
||||
$.div(
|
||||
{ className: 'about-updates-box' },
|
||||
$.div({ className: 'about-updates-status' }, this.renderUpdateStatus()),
|
||||
$.button(
|
||||
{
|
||||
className: 'btn about-update-action-button',
|
||||
disabled: this.shouldUpdateActionButtonBeDisabled(),
|
||||
onclick: this.executeUpdateAction.bind(this),
|
||||
style: {
|
||||
display: this.props.updateManager.state === UpdateManager.State.Unsupported ? 'none' : 'block'
|
||||
display:
|
||||
this.props.updateManager.state ===
|
||||
UpdateManager.State.Unsupported
|
||||
? 'none'
|
||||
: 'block'
|
||||
}
|
||||
},
|
||||
this.props.updateManager.state === 'update-available' ? 'Restart and install' : 'Check now'
|
||||
this.props.updateManager.state === 'update-available'
|
||||
? 'Restart and install'
|
||||
: 'Check now'
|
||||
)
|
||||
),
|
||||
$.div(
|
||||
{
|
||||
className: 'about-auto-updates',
|
||||
style: {
|
||||
display: this.props.updateManager.state === UpdateManager.State.Unsupported ? 'none' : 'block'
|
||||
display:
|
||||
this.props.updateManager.state === UpdateManager.State.Unsupported
|
||||
? 'none'
|
||||
: 'block'
|
||||
}
|
||||
},
|
||||
$.label({},
|
||||
$.input(
|
||||
{
|
||||
className: 'input-checkbox',
|
||||
type: 'checkbox',
|
||||
checked: this.props.updateManager.getAutoUpdatesEnabled(),
|
||||
onchange: this.handleAutoUpdateCheckbox.bind(this)
|
||||
}
|
||||
),
|
||||
$.label(
|
||||
{},
|
||||
$.input({
|
||||
className: 'input-checkbox',
|
||||
type: 'checkbox',
|
||||
checked: this.props.updateManager.getAutoUpdatesEnabled(),
|
||||
onchange: this.handleAutoUpdateCheckbox.bind(this)
|
||||
}),
|
||||
$.span({}, 'Automatically download updates')
|
||||
)
|
||||
)
|
||||
|
@ -4,8 +4,7 @@ const etch = require('etch')
|
||||
Public: Abstract class for handling the initialization
|
||||
boilerplate of an Etch component.
|
||||
*/
|
||||
module.exports =
|
||||
class EtchComponent {
|
||||
module.exports = class EtchComponent {
|
||||
constructor (props) {
|
||||
this.props = props
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {CompositeDisposable} = require('atom')
|
||||
const { CompositeDisposable } = require('atom')
|
||||
const semver = require('semver')
|
||||
const UpdateManager = require('./update-manager')
|
||||
const About = require('./about')
|
||||
@ -16,20 +16,33 @@ module.exports = {
|
||||
this.createModel()
|
||||
|
||||
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion)
|
||||
if (atom.getReleaseChannel() === 'dev' || (availableVersion && semver.lte(availableVersion, atom.getVersion()))) {
|
||||
if (
|
||||
atom.getReleaseChannel() === 'dev' ||
|
||||
(availableVersion && semver.lte(availableVersion, atom.getVersion()))
|
||||
) {
|
||||
this.clearUpdateState()
|
||||
}
|
||||
|
||||
this.subscriptions.add(updateManager.onDidChange(() => {
|
||||
if (updateManager.getState() === UpdateManager.State.UpdateAvailableToInstall) {
|
||||
window.localStorage.setItem(AvailableUpdateVersion, updateManager.getAvailableVersion())
|
||||
this.showStatusBarIfNeeded()
|
||||
}
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
updateManager.onDidChange(() => {
|
||||
if (
|
||||
updateManager.getState() ===
|
||||
UpdateManager.State.UpdateAvailableToInstall
|
||||
) {
|
||||
window.localStorage.setItem(
|
||||
AvailableUpdateVersion,
|
||||
updateManager.getAvailableVersion()
|
||||
)
|
||||
this.showStatusBarIfNeeded()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
this.subscriptions.add(atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
|
||||
this.clearUpdateState()
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
|
||||
this.clearUpdateState()
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {Emitter, CompositeDisposable} = require('atom')
|
||||
const { Emitter, CompositeDisposable } = require('atom')
|
||||
|
||||
const Unsupported = 'unsupported'
|
||||
const Idle = 'idle'
|
||||
@ -27,7 +27,7 @@ let UpdateManager = class UpdateManager {
|
||||
atom.autoUpdater.onDidBeginDownloadingUpdate(() => {
|
||||
this.setState(DownloadingUpdate)
|
||||
}),
|
||||
atom.autoUpdater.onDidCompleteDownloadingUpdate(({releaseVersion}) => {
|
||||
atom.autoUpdater.onDidCompleteDownloadingUpdate(({ releaseVersion }) => {
|
||||
this.setAvailableVersion(releaseVersion)
|
||||
}),
|
||||
atom.autoUpdater.onUpdateNotAvailable(() => {
|
||||
@ -36,7 +36,7 @@ let UpdateManager = class UpdateManager {
|
||||
atom.autoUpdater.onUpdateError(() => {
|
||||
this.setState(ErrorState)
|
||||
}),
|
||||
atom.config.observe('core.automaticallyUpdate', (value) => {
|
||||
atom.config.observe('core.automaticallyUpdate', value => {
|
||||
this.autoUpdatesEnabled = value
|
||||
this.emitDidChange()
|
||||
})
|
||||
@ -61,7 +61,9 @@ let UpdateManager = class UpdateManager {
|
||||
}
|
||||
|
||||
getAutoUpdatesEnabled () {
|
||||
return this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
|
||||
return (
|
||||
this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
|
||||
)
|
||||
}
|
||||
|
||||
setAutoUpdatesEnabled (enabled) {
|
||||
@ -82,7 +84,9 @@ let UpdateManager = class UpdateManager {
|
||||
}
|
||||
|
||||
resetState () {
|
||||
this.state = atom.autoUpdater.platformSupportsUpdates() ? atom.autoUpdater.getState() : Unsupported
|
||||
this.state = atom.autoUpdater.platformSupportsUpdates()
|
||||
? atom.autoUpdater.getState()
|
||||
: Unsupported
|
||||
this.emitDidChange()
|
||||
}
|
||||
|
||||
@ -128,7 +132,8 @@ let UpdateManager = class UpdateManager {
|
||||
appVersion = `v${appVersion}`
|
||||
}
|
||||
|
||||
const releaseRepo = appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'
|
||||
const releaseRepo =
|
||||
appVersion.indexOf('nightly') > -1 ? 'atom-nightly-releases' : 'atom'
|
||||
return `https://github.com/atom/${releaseRepo}/releases/tag/${appVersion}`
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
|
||||
describe('About', () => {
|
||||
let workspaceElement
|
||||
|
||||
@ -9,7 +7,7 @@ describe('About', () => {
|
||||
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
|
||||
storage[key] = value
|
||||
})
|
||||
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
|
||||
spyOn(window.localStorage, 'getItem').andCallFake(key => {
|
||||
return storage[key]
|
||||
})
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
const { conditionPromise } = require('./helpers/async-spec-helpers')
|
||||
const MockUpdater = require('./mocks/updater')
|
||||
|
||||
describe('the status bar', () => {
|
||||
@ -11,7 +11,7 @@ describe('the status bar', () => {
|
||||
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
|
||||
storage[key] = value
|
||||
})
|
||||
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
|
||||
spyOn(window.localStorage, 'getItem').andCallFake(key => {
|
||||
return storage[key]
|
||||
})
|
||||
spyOn(atom, 'getVersion').andCallFake(() => {
|
||||
|
@ -1,36 +1,7 @@
|
||||
/** @babel */
|
||||
|
||||
const {now} = Date
|
||||
const {setTimeout} = global
|
||||
|
||||
export function beforeEach (fn) {
|
||||
global.beforeEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function afterEach (fn) {
|
||||
global.afterEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
module.exports[name] = function (description, fn) {
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
const { now } = Date
|
||||
const { setTimeout } = global
|
||||
|
||||
export async function conditionPromise (condition) {
|
||||
const startTime = now()
|
||||
@ -53,13 +24,3 @@ export function timeoutPromise (timeout) {
|
||||
setTimeout(resolve, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
function waitsForPromise (fn) {
|
||||
const promise = fn()
|
||||
global.waitsFor('spec promise to resolve', function (done) {
|
||||
promise.then(done, function (error) {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
finishDownloadingUpdate (releaseVersion) {
|
||||
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {releaseVersion})
|
||||
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {
|
||||
releaseVersion
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,24 @@ describe('UpdateManager', () => {
|
||||
|
||||
describe('::getReleaseNotesURLForVersion', () => {
|
||||
it('returns atom.io releases when dev version', () => {
|
||||
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d')).toContain('atom.io/releases')
|
||||
expect(
|
||||
updateManager.getReleaseNotesURLForVersion('1.7.0-dev-e44b57d')
|
||||
).toContain('atom.io/releases')
|
||||
})
|
||||
|
||||
it('returns the page for the release when not a dev version', () => {
|
||||
expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain('atom/atom/releases/tag/v1.7.0')
|
||||
expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain('atom/atom/releases/tag/v1.7.0')
|
||||
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-beta10')).toContain('atom/atom/releases/tag/v1.7.0-beta10')
|
||||
expect(updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10')).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10')
|
||||
expect(updateManager.getReleaseNotesURLForVersion('1.7.0')).toContain(
|
||||
'atom/atom/releases/tag/v1.7.0'
|
||||
)
|
||||
expect(updateManager.getReleaseNotesURLForVersion('v1.7.0')).toContain(
|
||||
'atom/atom/releases/tag/v1.7.0'
|
||||
)
|
||||
expect(
|
||||
updateManager.getReleaseNotesURLForVersion('1.7.0-beta10')
|
||||
).toContain('atom/atom/releases/tag/v1.7.0-beta10')
|
||||
expect(
|
||||
updateManager.getReleaseNotesURLForVersion('1.7.0-nightly10')
|
||||
).toContain('atom/atom-nightly-releases/releases/tag/v1.7.0-nightly10')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,4 @@
|
||||
const {shell} = require('electron')
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./helpers/async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
const { shell } = require('electron')
|
||||
const main = require('../lib/main')
|
||||
const AboutView = require('../lib/components/about-view')
|
||||
const UpdateView = require('../lib/components/update-view')
|
||||
@ -17,7 +16,7 @@ describe('UpdateView', () => {
|
||||
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
|
||||
storage[key] = value
|
||||
})
|
||||
spyOn(window.localStorage, 'getItem').andCallFake((key) => {
|
||||
spyOn(window.localStorage, 'getItem').andCallFake(key => {
|
||||
return storage[key]
|
||||
})
|
||||
|
||||
@ -45,13 +44,19 @@ describe('UpdateView', () => {
|
||||
})
|
||||
|
||||
it('hides the auto update UI and shows the update instructions link', async () => {
|
||||
expect(aboutElement.querySelector('.about-update-action-button')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-auto-updates')).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button')
|
||||
).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-auto-updates')
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
it('opens the update instructions page when the instructions link is clicked', async () => {
|
||||
spyOn(shell, 'openExternal')
|
||||
let link = aboutElement.querySelector('.app-unsupported .about-updates-instructions')
|
||||
let link = aboutElement.querySelector(
|
||||
'.app-unsupported .about-updates-instructions'
|
||||
)
|
||||
link.click()
|
||||
|
||||
let args = shell.openExternal.mostRecentCall.args
|
||||
@ -72,66 +77,116 @@ describe('UpdateView', () => {
|
||||
})
|
||||
|
||||
it('shows the correct panels when the app checks for updates and there is no update available', async () => {
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
|
||||
MockUpdater.checkForUpdate()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).toBeVisible()
|
||||
|
||||
MockUpdater.updateNotAvailable()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
it('shows the correct panels when the app checks for updates and encounters an error', async () => {
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
|
||||
MockUpdater.checkForUpdate()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).toBeVisible()
|
||||
|
||||
spyOn(atom.autoUpdater, 'getErrorMessage').andReturn('an error message')
|
||||
MockUpdater.updateError()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-update-error')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-error-message').textContent).toBe('an error message')
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
|
||||
expect(
|
||||
aboutElement.querySelector('.app-error-message').textContent
|
||||
).toBe('an error message')
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(false)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Check now')
|
||||
})
|
||||
|
||||
it('shows the correct panels and button states when the app checks for updates and an update is downloaded', async () => {
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(false)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Check now')
|
||||
|
||||
MockUpdater.checkForUpdate()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
|
||||
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(true)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Check now')
|
||||
|
||||
MockUpdater.downloadUpdate()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-downloading-update')
|
||||
).toBeVisible()
|
||||
// TODO: at some point it would be nice to be able to cancel an update download, and then this would be a cancel button
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(true)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Check now')
|
||||
|
||||
MockUpdater.finishDownloadingUpdate('42.0.0')
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-downloading-update')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-downloading-update')
|
||||
).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-update-available-to-install')
|
||||
).toBeVisible()
|
||||
|
||||
expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42.0.0')
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Restart and install')
|
||||
expect(
|
||||
aboutElement.querySelector(
|
||||
'.app-update-available-to-install .about-updates-version'
|
||||
).textContent
|
||||
).toBe('42.0.0')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(false)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Restart and install')
|
||||
})
|
||||
|
||||
it('opens the release notes for the downloaded release when the release notes link are clicked', async () => {
|
||||
@ -139,7 +194,9 @@ describe('UpdateView', () => {
|
||||
await scheduler.getNextUpdatePromise()
|
||||
|
||||
spyOn(shell, 'openExternal')
|
||||
let link = aboutElement.querySelector('.app-update-available-to-install .about-updates-release-notes')
|
||||
let link = aboutElement.querySelector(
|
||||
'.app-update-available-to-install .about-updates-release-notes'
|
||||
)
|
||||
link.click()
|
||||
|
||||
let args = shell.openExternal.mostRecentCall.args
|
||||
@ -168,10 +225,18 @@ describe('UpdateView', () => {
|
||||
updateManager.resetState()
|
||||
|
||||
await scheduler.getNextUpdatePromise()
|
||||
expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(true)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Check now')
|
||||
expect(
|
||||
aboutElement.querySelector('.app-checking-for-updates')
|
||||
).not.toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.app-downloading-update')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(true)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Check now')
|
||||
})
|
||||
|
||||
describe('when core.automaticallyUpdate is toggled', () => {
|
||||
@ -181,36 +246,66 @@ describe('UpdateView', () => {
|
||||
})
|
||||
|
||||
it('shows the auto update UI', async () => {
|
||||
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Atom will check for updates automatically')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-auto-updates input').checked
|
||||
).toBe(true)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
.textContent
|
||||
).toBe('Atom will check for updates automatically')
|
||||
|
||||
atom.config.set('core.automaticallyUpdate', false)
|
||||
await scheduler.getNextUpdatePromise()
|
||||
|
||||
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Automatic updates are disabled please check manually')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-auto-updates input').checked
|
||||
).toBe(false)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
.textContent
|
||||
).toBe('Automatic updates are disabled please check manually')
|
||||
})
|
||||
|
||||
it('updates config and the UI when the checkbox is used to toggle', async () => {
|
||||
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-auto-updates input').checked
|
||||
).toBe(true)
|
||||
|
||||
aboutElement.querySelector('.about-auto-updates input').click()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
|
||||
expect(atom.config.get('core.automaticallyUpdate')).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Automatic updates are disabled please check manually')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-auto-updates input').checked
|
||||
).toBe(false)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
.textContent
|
||||
).toBe('Automatic updates are disabled please check manually')
|
||||
|
||||
aboutElement.querySelector('.about-auto-updates input').click()
|
||||
await scheduler.getNextUpdatePromise()
|
||||
|
||||
expect(atom.config.get('core.automaticallyUpdate')).toBe(true)
|
||||
expect(aboutElement.querySelector('.about-auto-updates input').checked).toBe(true)
|
||||
expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.about-default-update-message').textContent).toBe('Atom will check for updates automatically')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-auto-updates input').checked
|
||||
).toBe(true)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector('.about-default-update-message')
|
||||
.textContent
|
||||
).toBe('Atom will check for updates automatically')
|
||||
})
|
||||
|
||||
describe('checking for updates', function () {
|
||||
@ -271,10 +366,20 @@ describe('UpdateView', () => {
|
||||
updateManager = main.model.state.updateManager
|
||||
scheduler = AboutView.getScheduler()
|
||||
|
||||
expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
|
||||
expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42.0.0')
|
||||
expect(aboutElement.querySelector('.about-update-action-button').disabled).toBe(false)
|
||||
expect(aboutElement.querySelector('.about-update-action-button').textContent).toBe('Restart and install')
|
||||
expect(
|
||||
aboutElement.querySelector('.app-update-available-to-install')
|
||||
).toBeVisible()
|
||||
expect(
|
||||
aboutElement.querySelector(
|
||||
'.app-update-available-to-install .about-updates-version'
|
||||
).textContent
|
||||
).toBe('42.0.0')
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').disabled
|
||||
).toBe(false)
|
||||
expect(
|
||||
aboutElement.querySelector('.about-update-action-button').textContent
|
||||
).toBe('Restart and install')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -13,7 +13,9 @@ module.exports = {
|
||||
const names = atom.packages.getAvailablePackageNames()
|
||||
for (let name of names) {
|
||||
if (atom.packages.isBundledPackage(name)) {
|
||||
const isDuplicatedPackage = await this.isInstalledAsCommunityPackage(name)
|
||||
const isDuplicatedPackage = await this.isInstalledAsCommunityPackage(
|
||||
name
|
||||
)
|
||||
if (isDuplicatedPackage) {
|
||||
duplicatePackages.push(name)
|
||||
}
|
||||
|
@ -17,20 +17,49 @@ describe('dalek', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
availablePackages = {
|
||||
'an-unduplicated-installed-package': path.join('Users', 'username', '.atom', 'packages', 'an-unduplicated-installed-package'),
|
||||
'duplicated-package': path.join('Users', 'username', '.atom', 'packages', 'duplicated-package'),
|
||||
'unduplicated-package': path.join(`${atom.getLoadSettings().resourcePath}`, 'node_modules', 'unduplicated-package')
|
||||
'an-unduplicated-installed-package': path.join(
|
||||
'Users',
|
||||
'username',
|
||||
'.atom',
|
||||
'packages',
|
||||
'an-unduplicated-installed-package'
|
||||
),
|
||||
'duplicated-package': path.join(
|
||||
'Users',
|
||||
'username',
|
||||
'.atom',
|
||||
'packages',
|
||||
'duplicated-package'
|
||||
),
|
||||
'unduplicated-package': path.join(
|
||||
`${atom.getLoadSettings().resourcePath}`,
|
||||
'node_modules',
|
||||
'unduplicated-package'
|
||||
)
|
||||
}
|
||||
|
||||
atom.devMode = false
|
||||
bundledPackages = ['duplicated-package', 'unduplicated-package']
|
||||
packageDirPaths = [path.join('Users', 'username', '.atom', 'packages')]
|
||||
sandbox = sinon.sandbox.create()
|
||||
sandbox.stub(dalek, 'realpath').callsFake((filePath) => Promise.resolve(realPaths[filePath] || filePath))
|
||||
sandbox.stub(atom.packages, 'isBundledPackage').callsFake((packageName) => { return bundledPackages.includes(packageName) })
|
||||
sandbox.stub(atom.packages, 'getAvailablePackageNames').callsFake(() => Object.keys(availablePackages))
|
||||
sandbox.stub(atom.packages, 'getPackageDirPaths').callsFake(() => { return packageDirPaths })
|
||||
sandbox.stub(fs, 'existsSync').callsFake((candidate) => { return Object.values(availablePackages).includes(candidate) && !candidate.includes(atom.getLoadSettings().resourcePath) })
|
||||
sandbox
|
||||
.stub(dalek, 'realpath')
|
||||
.callsFake(filePath => Promise.resolve(realPaths[filePath] || filePath))
|
||||
sandbox.stub(atom.packages, 'isBundledPackage').callsFake(packageName => {
|
||||
return bundledPackages.includes(packageName)
|
||||
})
|
||||
sandbox
|
||||
.stub(atom.packages, 'getAvailablePackageNames')
|
||||
.callsFake(() => Object.keys(availablePackages))
|
||||
sandbox.stub(atom.packages, 'getPackageDirPaths').callsFake(() => {
|
||||
return packageDirPaths
|
||||
})
|
||||
sandbox.stub(fs, 'existsSync').callsFake(candidate => {
|
||||
return (
|
||||
Object.values(availablePackages).includes(candidate) &&
|
||||
!candidate.includes(atom.getLoadSettings().resourcePath)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
@ -54,7 +83,13 @@ describe('dalek', function () {
|
||||
describe('when a package is symlinked into the package directory', async function () {
|
||||
beforeEach(function () {
|
||||
const realPath = path.join('Users', 'username', 'duplicated-package')
|
||||
const packagePath = path.join('Users', 'username', '.atom', 'packages', 'duplicated-package')
|
||||
const packagePath = path.join(
|
||||
'Users',
|
||||
'username',
|
||||
'.atom',
|
||||
'packages',
|
||||
'duplicated-package'
|
||||
)
|
||||
realPaths[packagePath] = realPath
|
||||
})
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
const createRunner = require('atom-mocha-test-runner').createRunner
|
||||
module.exports = createRunner({testSuffixes: ['test.js']})
|
||||
module.exports = createRunner({ testSuffixes: ['test.js'] })
|
||||
|
@ -2,7 +2,7 @@
|
||||
/** @jsx etch.dom */
|
||||
|
||||
import _ from 'underscore-plus'
|
||||
import {CompositeDisposable} from 'atom'
|
||||
import { CompositeDisposable } from 'atom'
|
||||
import etch from 'etch'
|
||||
import fs from 'fs-plus'
|
||||
import Grim from 'grim'
|
||||
@ -11,23 +11,45 @@ import path from 'path'
|
||||
import shell from 'shell'
|
||||
|
||||
export default class DeprecationCopView {
|
||||
constructor ({uri}) {
|
||||
constructor ({ uri }) {
|
||||
this.uri = uri
|
||||
this.subscriptions = new CompositeDisposable
|
||||
this.subscriptions.add(Grim.on('updated', () => { etch.update(this) }))
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.subscriptions.add(
|
||||
Grim.on('updated', () => {
|
||||
etch.update(this)
|
||||
})
|
||||
)
|
||||
// TODO: Remove conditional when the new StyleManager deprecation APIs reach stable.
|
||||
if (atom.styles.onDidUpdateDeprecations) {
|
||||
this.subscriptions.add(atom.styles.onDidUpdateDeprecations(() => { etch.update(this) }))
|
||||
this.subscriptions.add(
|
||||
atom.styles.onDidUpdateDeprecations(() => {
|
||||
etch.update(this)
|
||||
})
|
||||
)
|
||||
}
|
||||
etch.initialize(this)
|
||||
this.subscriptions.add(atom.commands.add(this.element, {
|
||||
'core:move-up': () => { this.scrollUp() },
|
||||
'core:move-down': () => { this.scrollDown() },
|
||||
'core:page-up': () => { this.pageUp() },
|
||||
'core:page-down': () => { this.pageDown() },
|
||||
'core:move-to-top': () => { this.scrollToTop() },
|
||||
'core:move-to-bottom': () => { this.scrollToBottom() }
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
atom.commands.add(this.element, {
|
||||
'core:move-up': () => {
|
||||
this.scrollUp()
|
||||
},
|
||||
'core:move-down': () => {
|
||||
this.scrollDown()
|
||||
},
|
||||
'core:page-up': () => {
|
||||
this.pageUp()
|
||||
},
|
||||
'core:page-down': () => {
|
||||
this.pageDown()
|
||||
},
|
||||
'core:move-to-top': () => {
|
||||
this.scrollToTop()
|
||||
},
|
||||
'core:move-to-bottom': () => {
|
||||
this.scrollToBottom()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
serialize () {
|
||||
@ -49,25 +71,35 @@ export default class DeprecationCopView {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='deprecation-cop pane-item native-key-bindings' tabIndex='-1'>
|
||||
<div
|
||||
className='deprecation-cop pane-item native-key-bindings'
|
||||
tabIndex='-1'
|
||||
>
|
||||
<div className='panel'>
|
||||
<div className='padded deprecation-overview'>
|
||||
<div className='pull-right btn-group'>
|
||||
<button
|
||||
className='btn btn-primary check-for-update'
|
||||
onclick={(event) => {
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.checkForUpdates()
|
||||
}}>Check for Updates</button>
|
||||
}}
|
||||
>
|
||||
Check for Updates
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='panel-heading'><span>Deprecated calls</span></div>
|
||||
<div className='panel-heading'>
|
||||
<span>Deprecated calls</span>
|
||||
</div>
|
||||
<ul className='list-tree has-collapsable-children'>
|
||||
{this.renderDeprecatedCalls()}
|
||||
</ul>
|
||||
|
||||
<div className='panel-heading'><span>Deprecated selectors</span></div>
|
||||
<div className='panel-heading'>
|
||||
<span>Deprecated selectors</span>
|
||||
</div>
|
||||
<ul className='selectors list-tree has-collapsable-children'>
|
||||
{this.renderDeprecatedSelectors()}
|
||||
</ul>
|
||||
@ -82,37 +114,57 @@ export default class DeprecationCopView {
|
||||
if (packageNames.length === 0) {
|
||||
return <li className='list-item'>No deprecated calls</li>
|
||||
} else {
|
||||
return packageNames.sort().map((packageName) => (
|
||||
return packageNames.sort().map(packageName => (
|
||||
<li className='deprecation list-nested-item collapsed'>
|
||||
<div className='deprecation-info list-item' onclick={(event) => event.target.parentElement.classList.toggle('collapsed')}>
|
||||
<div
|
||||
className='deprecation-info list-item'
|
||||
onclick={event =>
|
||||
event.target.parentElement.classList.toggle('collapsed')
|
||||
}
|
||||
>
|
||||
<span className='text-highlight'>{packageName || 'atom core'}</span>
|
||||
<span>{` (${_.pluralize(deprecationsByPackageName[packageName].length, 'deprecation')})`}</span>
|
||||
<span>{` (${_.pluralize(
|
||||
deprecationsByPackageName[packageName].length,
|
||||
'deprecation'
|
||||
)})`}</span>
|
||||
</div>
|
||||
|
||||
<ul className='list'>
|
||||
{this.renderPackageActionsIfNeeded(packageName)}
|
||||
{deprecationsByPackageName[packageName].map(({deprecation, stack}) => (
|
||||
<li className='list-item deprecation-detail'>
|
||||
<span className='text-warning icon icon-alert' />
|
||||
<div className='list-item deprecation-message' innerHTML={marked(deprecation.getMessage())} />
|
||||
{this.renderIssueURLIfNeeded(packageName, deprecation, this.buildIssueURL(packageName, deprecation, stack))}
|
||||
<div className='stack-trace'>
|
||||
{stack.map(({functionName, location}) => (
|
||||
<div className='stack-line'>
|
||||
<span>{functionName}</span>
|
||||
<span> - </span>
|
||||
<a
|
||||
className='stack-line-location'
|
||||
href={location}
|
||||
onclick={(event) => {
|
||||
event.preventDefault()
|
||||
this.openLocation(location)
|
||||
}}>{location}</a>
|
||||
{deprecationsByPackageName[packageName].map(
|
||||
({ deprecation, stack }) => (
|
||||
<li className='list-item deprecation-detail'>
|
||||
<span className='text-warning icon icon-alert' />
|
||||
<div
|
||||
className='list-item deprecation-message'
|
||||
innerHTML={marked(deprecation.getMessage())}
|
||||
/>
|
||||
{this.renderIssueURLIfNeeded(
|
||||
packageName,
|
||||
deprecation,
|
||||
this.buildIssueURL(packageName, deprecation, stack)
|
||||
)}
|
||||
<div className='stack-trace'>
|
||||
{stack.map(({ functionName, location }) => (
|
||||
<div className='stack-line'>
|
||||
<span>{functionName}</span>
|
||||
<span> - </span>
|
||||
<a
|
||||
className='stack-line-location'
|
||||
href={location}
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.openLocation(location)
|
||||
}}
|
||||
>
|
||||
{location}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
))
|
||||
@ -123,41 +175,61 @@ export default class DeprecationCopView {
|
||||
const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName()
|
||||
const packageNames = Object.keys(deprecationsByPackageName)
|
||||
if (packageNames.length === 0) {
|
||||
return (
|
||||
<li className='list-item'>No deprecated selectors</li>
|
||||
)
|
||||
return <li className='list-item'>No deprecated selectors</li>
|
||||
} else {
|
||||
return packageNames.map((packageName) => (
|
||||
return packageNames.map(packageName => (
|
||||
<li className='deprecation list-nested-item collapsed'>
|
||||
<div className='deprecation-info list-item' onclick={(event) => event.target.parentElement.classList.toggle('collapsed')}>
|
||||
<div
|
||||
className='deprecation-info list-item'
|
||||
onclick={event =>
|
||||
event.target.parentElement.classList.toggle('collapsed')
|
||||
}
|
||||
>
|
||||
<span className='text-highlight'>{packageName}</span>
|
||||
</div>
|
||||
|
||||
<ul className='list'>
|
||||
{this.renderPackageActionsIfNeeded(packageName)}
|
||||
{deprecationsByPackageName[packageName].map(({packagePath, sourcePath, deprecation}) => {
|
||||
const relativeSourcePath = path.relative(packagePath, sourcePath)
|
||||
const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``
|
||||
const issueBody = `In \`${relativeSourcePath}\`: \n\n${deprecation.message}`
|
||||
return (
|
||||
<li className='list-item source-file'>
|
||||
<a
|
||||
className='source-url'
|
||||
href={sourcePath}
|
||||
onclick={(event) => {
|
||||
event.preventDefault()
|
||||
this.openLocation(sourcePath)
|
||||
}}>{relativeSourcePath}</a>
|
||||
<ul className='list'>
|
||||
<li className='list-item deprecation-detail'>
|
||||
<span className='text-warning icon icon-alert' />
|
||||
<div className='list-item deprecation-message' innerHTML={marked(deprecation.message)} />
|
||||
{this.renderSelectorIssueURLIfNeeded(packageName, issueTitle, issueBody)}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{deprecationsByPackageName[packageName].map(
|
||||
({ packagePath, sourcePath, deprecation }) => {
|
||||
const relativeSourcePath = path.relative(
|
||||
packagePath,
|
||||
sourcePath
|
||||
)
|
||||
const issueTitle = `Deprecated selector in \`${relativeSourcePath}\``
|
||||
const issueBody = `In \`${relativeSourcePath}\`: \n\n${
|
||||
deprecation.message
|
||||
}`
|
||||
return (
|
||||
<li className='list-item source-file'>
|
||||
<a
|
||||
className='source-url'
|
||||
href={sourcePath}
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.openLocation(sourcePath)
|
||||
}}
|
||||
>
|
||||
{relativeSourcePath}
|
||||
</a>
|
||||
<ul className='list'>
|
||||
<li className='list-item deprecation-detail'>
|
||||
<span className='text-warning icon icon-alert' />
|
||||
<div
|
||||
className='list-item deprecation-message'
|
||||
innerHTML={marked(deprecation.message)}
|
||||
/>
|
||||
{this.renderSelectorIssueURLIfNeeded(
|
||||
packageName,
|
||||
issueTitle,
|
||||
issueBody
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
))
|
||||
@ -171,17 +243,23 @@ export default class DeprecationCopView {
|
||||
<div className='btn-group'>
|
||||
<button
|
||||
className='btn check-for-update'
|
||||
onclick={(event) => {
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.checkForUpdates()
|
||||
}}>Check for Update</button>
|
||||
}}
|
||||
>
|
||||
Check for Update
|
||||
</button>
|
||||
<button
|
||||
className='btn disable-package'
|
||||
data-package-name={packageName}
|
||||
onclick={(event) => {
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.disablePackage(packageName)
|
||||
}}>Disable Package</button>
|
||||
}}
|
||||
>
|
||||
Disable Package
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -191,13 +269,18 @@ export default class DeprecationCopView {
|
||||
}
|
||||
|
||||
encodeURI (str) {
|
||||
return encodeURI(str).replace(/#/g, '%23').replace(/;/g, '%3B').replace(/%20/g, '+')
|
||||
return encodeURI(str)
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/;/g, '%3B')
|
||||
.replace(/%20/g, '+')
|
||||
}
|
||||
|
||||
renderSelectorIssueURLIfNeeded (packageName, issueTitle, issueBody) {
|
||||
const repoURL = this.getRepoURL(packageName)
|
||||
if (repoURL) {
|
||||
const issueURL = `${repoURL}/issues/new?title=${this.encodeURI(issueTitle)}&body=${this.encodeURI(issueBody)}`
|
||||
const issueURL = `${repoURL}/issues/new?title=${this.encodeURI(
|
||||
issueTitle
|
||||
)}&body=${this.encodeURI(issueBody)}`
|
||||
return (
|
||||
<div className='btn-toolbar'>
|
||||
<button
|
||||
@ -205,10 +288,13 @@ export default class DeprecationCopView {
|
||||
data-issue-title={issueTitle}
|
||||
data-repo-url={repoURL}
|
||||
data-issue-url={issueURL}
|
||||
onclick={(event) => {
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.openIssueURL(repoURL, issueURL, issueTitle)
|
||||
}}>Report Issue</button>
|
||||
}}
|
||||
>
|
||||
Report Issue
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
@ -227,10 +313,13 @@ export default class DeprecationCopView {
|
||||
data-issue-title={issueTitle}
|
||||
data-repo-url={repoURL}
|
||||
data-issue-url={issueURL}
|
||||
onclick={(event) => {
|
||||
onclick={event => {
|
||||
event.preventDefault()
|
||||
this.openIssueURL(repoURL, issueURL, issueTitle)
|
||||
}}>Report Issue</button>
|
||||
}}
|
||||
>
|
||||
Report Issue
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
@ -242,9 +331,13 @@ export default class DeprecationCopView {
|
||||
const repoURL = this.getRepoURL(packageName)
|
||||
if (repoURL) {
|
||||
const title = `${deprecation.getOriginName()} is deprecated.`
|
||||
const stacktrace = stack.map(({functionName, location}) => `${functionName} (${location})`).join("\n")
|
||||
const stacktrace = stack
|
||||
.map(({ functionName, location }) => `${functionName} (${location})`)
|
||||
.join('\n')
|
||||
const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\``
|
||||
return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI(body)}`
|
||||
return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI(
|
||||
body
|
||||
)}`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@ -266,13 +359,16 @@ export default class DeprecationCopView {
|
||||
const url = 'https://api.github.com/search/issues'
|
||||
const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, '')
|
||||
const query = `${issueTitle} repo:${repo}`
|
||||
const response = await window.fetch(`${url}?q=${encodeURI(query)}&sort=created`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'Content-Type': 'application/json'
|
||||
const response = await window.fetch(
|
||||
`${url}?q=${encodeURI(query)}&sort=created`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
@ -284,21 +380,25 @@ export default class DeprecationCopView {
|
||||
}
|
||||
}
|
||||
|
||||
return (issues.open || issues.closed)
|
||||
return issues.open || issues.closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async shortenURL (url) {
|
||||
let encodedUrl = encodeURIComponent(url).substr(0, 5000) // is.gd has 5000 char limit
|
||||
let incompletePercentEncoding = encodedUrl.indexOf('%', encodedUrl.length - 2)
|
||||
if (incompletePercentEncoding >= 0) { // Handle an incomplete % encoding cut-off
|
||||
let incompletePercentEncoding = encodedUrl.indexOf(
|
||||
'%',
|
||||
encodedUrl.length - 2
|
||||
)
|
||||
if (incompletePercentEncoding >= 0) {
|
||||
// Handle an incomplete % encoding cut-off
|
||||
encodedUrl = encodedUrl.substr(0, incompletePercentEncoding)
|
||||
}
|
||||
|
||||
let result = await fetch('https://is.gd/create.php?format=simple', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `url=${encodedUrl}`
|
||||
})
|
||||
|
||||
@ -307,8 +407,14 @@ export default class DeprecationCopView {
|
||||
|
||||
getRepoURL (packageName) {
|
||||
const loadedPackage = atom.packages.getLoadedPackage(packageName)
|
||||
if (loadedPackage && loadedPackage.metadata && loadedPackage.metadata.repository) {
|
||||
const url = loadedPackage.metadata.repository.url || loadedPackage.metadata.repository
|
||||
if (
|
||||
loadedPackage &&
|
||||
loadedPackage.metadata &&
|
||||
loadedPackage.metadata.repository
|
||||
) {
|
||||
const url =
|
||||
loadedPackage.metadata.repository.url ||
|
||||
loadedPackage.metadata.repository
|
||||
return url.replace(/\.git$/, '')
|
||||
} else {
|
||||
return null
|
||||
@ -330,8 +436,9 @@ export default class DeprecationCopView {
|
||||
packageName = (this.getPackageName(stack) || '').toLowerCase()
|
||||
}
|
||||
|
||||
deprecatedCallsByPackageName[packageName] = deprecatedCallsByPackageName[packageName] || []
|
||||
deprecatedCallsByPackageName[packageName].push({deprecation, stack})
|
||||
deprecatedCallsByPackageName[packageName] =
|
||||
deprecatedCallsByPackageName[packageName] || []
|
||||
deprecatedCallsByPackageName[packageName].push({ deprecation, stack })
|
||||
}
|
||||
}
|
||||
return deprecatedCallsByPackageName
|
||||
@ -352,11 +459,18 @@ export default class DeprecationCopView {
|
||||
packagePath = ''
|
||||
} else {
|
||||
packageName = components[packagesComponentIndex + 1]
|
||||
packagePath = components.slice(0, packagesComponentIndex + 1).join(path.sep)
|
||||
packagePath = components
|
||||
.slice(0, packagesComponentIndex + 1)
|
||||
.join(path.sep)
|
||||
}
|
||||
|
||||
deprecatedSelectorsByPackageName[packageName] = deprecatedSelectorsByPackageName[packageName] || []
|
||||
deprecatedSelectorsByPackageName[packageName].push({packagePath, sourcePath, deprecation})
|
||||
deprecatedSelectorsByPackageName[packageName] =
|
||||
deprecatedSelectorsByPackageName[packageName] || []
|
||||
deprecatedSelectorsByPackageName[packageName].push({
|
||||
packagePath,
|
||||
sourcePath,
|
||||
deprecation
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,13 +480,16 @@ export default class DeprecationCopView {
|
||||
getPackageName (stack) {
|
||||
const packagePaths = this.getPackagePathsByPackageName()
|
||||
for (const [packageName, packagePath] of packagePaths) {
|
||||
if (packagePath.includes('.atom/dev/packages') || packagePath.includes('.atom/packages')) {
|
||||
if (
|
||||
packagePath.includes('.atom/dev/packages') ||
|
||||
packagePath.includes('.atom/packages')
|
||||
) {
|
||||
packagePaths.set(packageName, fs.absolute(packagePath))
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i < stack.length; i++) {
|
||||
const {fileName} = stack[i]
|
||||
const { fileName } = stack[i]
|
||||
|
||||
// Empty when it was run from the dev console
|
||||
if (!fileName) {
|
||||
@ -426,7 +543,7 @@ export default class DeprecationCopView {
|
||||
if (process.platform === 'win32') {
|
||||
pathToOpen = pathToOpen.replace(/^\//, '')
|
||||
}
|
||||
atom.open({pathsToOpen: [pathToOpen]})
|
||||
atom.open({ pathsToOpen: [pathToOpen] })
|
||||
}
|
||||
|
||||
getURI () {
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {Disposable, CompositeDisposable} = require('atom')
|
||||
const { Disposable, CompositeDisposable } = require('atom')
|
||||
const DeprecationCopView = require('./deprecation-cop-view')
|
||||
const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view')
|
||||
const ViewURI = 'atom://deprecation-cop'
|
||||
@ -6,14 +6,18 @@ const ViewURI = 'atom://deprecation-cop'
|
||||
class DeprecationCopPackage {
|
||||
activate () {
|
||||
this.disposables = new CompositeDisposable()
|
||||
this.disposables.add(atom.workspace.addOpener((uri) => {
|
||||
if (uri === ViewURI) {
|
||||
return this.deserializeDeprecationCopView({uri})
|
||||
}
|
||||
}))
|
||||
this.disposables.add(atom.commands.add('atom-workspace', 'deprecation-cop:view', () => {
|
||||
atom.workspace.open(ViewURI)
|
||||
}))
|
||||
this.disposables.add(
|
||||
atom.workspace.addOpener(uri => {
|
||||
if (uri === ViewURI) {
|
||||
return this.deserializeDeprecationCopView({ uri })
|
||||
}
|
||||
})
|
||||
)
|
||||
this.disposables.add(
|
||||
atom.commands.add('atom-workspace', 'deprecation-cop:view', () => {
|
||||
atom.workspace.open(ViewURI)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
deactivate () {
|
||||
@ -30,9 +34,20 @@ class DeprecationCopPackage {
|
||||
|
||||
consumeStatusBar (statusBar) {
|
||||
const statusBarView = new DeprecationCopStatusBarView()
|
||||
const statusBarTile = statusBar.addRightTile({item: statusBarView, priority: 150})
|
||||
this.disposables.add(new Disposable(() => { statusBarView.destroy() }))
|
||||
this.disposables.add(new Disposable(() => { statusBarTile.destroy() }))
|
||||
const statusBarTile = statusBar.addRightTile({
|
||||
item: statusBarView,
|
||||
priority: 150
|
||||
})
|
||||
this.disposables.add(
|
||||
new Disposable(() => {
|
||||
statusBarView.destroy()
|
||||
})
|
||||
)
|
||||
this.disposables.add(
|
||||
new Disposable(() => {
|
||||
statusBarTile.destroy()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,19 @@ const fs = require('fs-plus')
|
||||
const path = require('path')
|
||||
const Watcher = require('./watcher')
|
||||
|
||||
module.exports =
|
||||
class BaseThemeWatcher extends Watcher {
|
||||
module.exports = class BaseThemeWatcher extends Watcher {
|
||||
constructor () {
|
||||
super()
|
||||
this.stylesheetsPath = path.dirname(atom.themes.resolveStylesheet('../static/atom.less'))
|
||||
this.stylesheetsPath = path.dirname(
|
||||
atom.themes.resolveStylesheet('../static/atom.less')
|
||||
)
|
||||
this.watch()
|
||||
}
|
||||
|
||||
watch () {
|
||||
const filePaths = fs.readdirSync(this.stylesheetsPath).filter(filePath => path.extname(filePath).includes('less'))
|
||||
const filePaths = fs
|
||||
.readdirSync(this.stylesheetsPath)
|
||||
.filter(filePath => path.extname(filePath).includes('less'))
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
this.watchFile(path.join(this.stylesheetsPath, filePath))
|
||||
|
@ -5,7 +5,9 @@ module.exports = {
|
||||
if (atom.packages.hasActivatedInitialPackages()) {
|
||||
this.startWatching()
|
||||
} else {
|
||||
this.activatedDisposable = atom.packages.onDidActivateInitialPackages(() => this.startWatching())
|
||||
this.activatedDisposable = atom.packages.onDidActivateInitialPackages(
|
||||
() => this.startWatching()
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@ -17,8 +19,12 @@ module.exports = {
|
||||
|
||||
startWatching () {
|
||||
const UIWatcher = require('./ui-watcher')
|
||||
this.uiWatcher = new UIWatcher({themeManager: atom.themes})
|
||||
this.commandDisposable = atom.commands.add('atom-workspace', 'dev-live-reload:reload-all', () => this.uiWatcher.reloadAll())
|
||||
this.uiWatcher = new UIWatcher({ themeManager: atom.themes })
|
||||
this.commandDisposable = atom.commands.add(
|
||||
'atom-workspace',
|
||||
'dev-live-reload:reload-all',
|
||||
() => this.uiWatcher.reloadAll()
|
||||
)
|
||||
if (this.activatedDisposable) this.activatedDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,7 @@ const fs = require('fs-plus')
|
||||
|
||||
const Watcher = require('./watcher')
|
||||
|
||||
module.exports =
|
||||
class PackageWatcher extends Watcher {
|
||||
module.exports = class PackageWatcher extends Watcher {
|
||||
static supportsPackage (pack, type) {
|
||||
if (pack.getType() === type && pack.getStylesheetPaths().length) return true
|
||||
return false
|
||||
@ -24,7 +23,9 @@ class PackageWatcher extends Watcher {
|
||||
|
||||
const stylesheetsPath = this.pack.getStylesheetsPath()
|
||||
|
||||
if (fs.isDirectorySync(stylesheetsPath)) this.watchDirectory(stylesheetsPath)
|
||||
if (fs.isDirectorySync(stylesheetsPath)) {
|
||||
this.watchDirectory(stylesheetsPath)
|
||||
}
|
||||
|
||||
const stylesheetPaths = new Set(this.pack.getStylesheetPaths())
|
||||
const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath)
|
||||
|
@ -1,10 +1,9 @@
|
||||
const {CompositeDisposable} = require('atom')
|
||||
const { CompositeDisposable } = require('atom')
|
||||
|
||||
const BaseThemeWatcher = require('./base-theme-watcher')
|
||||
const PackageWatcher = require('./package-watcher')
|
||||
|
||||
module.exports =
|
||||
class UIWatcher {
|
||||
module.exports = class UIWatcher {
|
||||
constructor () {
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.reloadAll = this.reloadAll.bind(this)
|
||||
@ -16,39 +15,63 @@ class UIWatcher {
|
||||
watchPackages () {
|
||||
this.watchedThemes = new Map()
|
||||
this.watchedPackages = new Map()
|
||||
for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) }
|
||||
for (const pack of atom.packages.getActivePackages()) { this.watchPackage(pack) }
|
||||
for (const theme of atom.themes.getActiveThemes()) {
|
||||
this.watchTheme(theme)
|
||||
}
|
||||
for (const pack of atom.packages.getActivePackages()) {
|
||||
this.watchPackage(pack)
|
||||
}
|
||||
this.watchForPackageChanges()
|
||||
}
|
||||
|
||||
watchForPackageChanges () {
|
||||
this.subscriptions.add(atom.themes.onDidChangeActiveThemes(() => {
|
||||
// We need to destroy all theme watchers as all theme packages are destroyed
|
||||
// when a theme changes.
|
||||
for (const theme of this.watchedThemes.values()) { theme.destroy() }
|
||||
this.subscriptions.add(
|
||||
atom.themes.onDidChangeActiveThemes(() => {
|
||||
// We need to destroy all theme watchers as all theme packages are destroyed
|
||||
// when a theme changes.
|
||||
for (const theme of this.watchedThemes.values()) {
|
||||
theme.destroy()
|
||||
}
|
||||
|
||||
this.watchedThemes.clear()
|
||||
this.watchedThemes.clear()
|
||||
|
||||
// Rewatch everything!
|
||||
for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) }
|
||||
}))
|
||||
// Rewatch everything!
|
||||
for (const theme of atom.themes.getActiveThemes()) {
|
||||
this.watchTheme(theme)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
this.subscriptions.add(atom.packages.onDidActivatePackage(pack => this.watchPackage(pack)))
|
||||
this.subscriptions.add(
|
||||
atom.packages.onDidActivatePackage(pack => this.watchPackage(pack))
|
||||
)
|
||||
|
||||
this.subscriptions.add(atom.packages.onDidDeactivatePackage(pack => {
|
||||
// This only handles packages - onDidChangeActiveThemes handles themes
|
||||
const watcher = this.watchedPackages.get(pack.name)
|
||||
if (watcher) watcher.destroy()
|
||||
this.watchedPackages.delete(pack.name)
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
atom.packages.onDidDeactivatePackage(pack => {
|
||||
// This only handles packages - onDidChangeActiveThemes handles themes
|
||||
const watcher = this.watchedPackages.get(pack.name)
|
||||
if (watcher) watcher.destroy()
|
||||
this.watchedPackages.delete(pack.name)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
watchTheme (theme) {
|
||||
if (PackageWatcher.supportsPackage(theme, 'theme')) this.watchedThemes.set(theme.name, this.createWatcher(new PackageWatcher(theme)))
|
||||
if (PackageWatcher.supportsPackage(theme, 'theme')) {
|
||||
this.watchedThemes.set(
|
||||
theme.name,
|
||||
this.createWatcher(new PackageWatcher(theme))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
watchPackage (pack) {
|
||||
if (PackageWatcher.supportsPackage(pack, 'atom')) this.watchedPackages.set(pack.name, this.createWatcher(new PackageWatcher(pack)))
|
||||
if (PackageWatcher.supportsPackage(pack, 'atom')) {
|
||||
this.watchedPackages.set(
|
||||
pack.name,
|
||||
this.createWatcher(new PackageWatcher(pack))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
createWatcher (watcher) {
|
||||
@ -56,7 +79,9 @@ class UIWatcher {
|
||||
console.log('Global changed, reloading all styles')
|
||||
this.reloadAll()
|
||||
})
|
||||
watcher.onDidDestroy(() => this.watchers.splice(this.watchers.indexOf(watcher), 1))
|
||||
watcher.onDidDestroy(() =>
|
||||
this.watchers.splice(this.watchers.indexOf(watcher), 1)
|
||||
)
|
||||
this.watchers.push(watcher)
|
||||
return watcher
|
||||
}
|
||||
@ -64,18 +89,26 @@ class UIWatcher {
|
||||
reloadAll () {
|
||||
this.baseTheme.loadAllStylesheets()
|
||||
for (const pack of atom.packages.getActivePackages()) {
|
||||
if (PackageWatcher.supportsPackage(pack, 'atom')) pack.reloadStylesheets()
|
||||
if (PackageWatcher.supportsPackage(pack, 'atom')) {
|
||||
pack.reloadStylesheets()
|
||||
}
|
||||
}
|
||||
|
||||
for (const theme of atom.themes.getActiveThemes()) {
|
||||
if (PackageWatcher.supportsPackage(theme, 'theme')) theme.reloadStylesheets()
|
||||
if (PackageWatcher.supportsPackage(theme, 'theme')) {
|
||||
theme.reloadStylesheets()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.subscriptions.dispose()
|
||||
this.baseTheme.destroy()
|
||||
for (const pack of this.watchedPackages.values()) { pack.destroy() }
|
||||
for (const theme of this.watchedThemes.values()) { theme.destroy() }
|
||||
for (const pack of this.watchedPackages.values()) {
|
||||
pack.destroy()
|
||||
}
|
||||
for (const theme of this.watchedThemes.values()) {
|
||||
theme.destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
const {CompositeDisposable, File, Directory, Emitter} = require('atom')
|
||||
const { CompositeDisposable, File, Directory, Emitter } = require('atom')
|
||||
const path = require('path')
|
||||
|
||||
module.exports =
|
||||
class Watcher {
|
||||
module.exports = class Watcher {
|
||||
constructor () {
|
||||
this.destroy = this.destroy.bind(this)
|
||||
this.emitter = new Emitter()
|
||||
@ -66,7 +65,10 @@ class Watcher {
|
||||
}
|
||||
|
||||
isInAsarArchive (pathToCheck) {
|
||||
const {resourcePath} = atom.getLoadSettings()
|
||||
return pathToCheck.startsWith(`${resourcePath}${path.sep}`) && path.extname(resourcePath) === '.asar'
|
||||
const { resourcePath } = atom.getLoadSettings()
|
||||
return (
|
||||
pathToCheck.startsWith(`${resourcePath}${path.sep}`) &&
|
||||
path.extname(resourcePath) === '.asar'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,9 @@
|
||||
/** @babel */
|
||||
|
||||
export function beforeEach (fn) {
|
||||
global.beforeEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function afterEach (fn) {
|
||||
global.afterEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
module.exports[name] = function (description, fn) {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export async function conditionPromise (condition, description = 'anonymous condition') {
|
||||
export async function conditionPromise (
|
||||
condition,
|
||||
description = 'anonymous condition'
|
||||
) {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
@ -55,49 +24,3 @@ export function timeoutPromise (timeout) {
|
||||
global.setTimeout(resolve, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
function waitsForPromise (fn) {
|
||||
const promise = fn()
|
||||
global.waitsFor('spec promise to resolve', function (done) {
|
||||
promise.then(done, function (error) {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`Timed out waiting for '${event}' event`))
|
||||
}, timeout)
|
||||
emitter.once(event, () => {
|
||||
clearTimeout(timeoutHandle)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function promisify (original) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, ...results) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(...results)
|
||||
}
|
||||
})
|
||||
|
||||
return original(...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function promisifySome (obj, fnNames) {
|
||||
const result = {}
|
||||
for (const fnName of fnNames) {
|
||||
result[fnName] = promisify(obj[fnName])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
|
||||
describe('Dev Live Reload', () => {
|
||||
describe('package activation', () => {
|
||||
let [pack, mainModule] = []
|
||||
@ -85,7 +83,9 @@ describe('Dev Live Reload', () => {
|
||||
|
||||
it('stops watching all files', async () => {
|
||||
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
|
||||
const {mainModule} = await atom.packages.activatePackage('dev-live-reload')
|
||||
const { mainModule } = await atom.packages.activatePackage(
|
||||
'dev-live-reload'
|
||||
)
|
||||
expect(mainModule.uiWatcher).not.toBeNull()
|
||||
|
||||
spyOn(mainModule.uiWatcher, 'destroy')
|
||||
@ -95,7 +95,9 @@ describe('Dev Live Reload', () => {
|
||||
})
|
||||
|
||||
it('unsubscribes from the onDidActivateInitialPackages subscription if it is disabled before all initial packages are activated', async () => {
|
||||
const {mainModule} = await atom.packages.activatePackage('dev-live-reload')
|
||||
const { mainModule } = await atom.packages.activatePackage(
|
||||
'dev-live-reload'
|
||||
)
|
||||
expect(mainModule.activatedDisposable.disposed).toBe(false)
|
||||
|
||||
await atom.packages.deactivatePackage('dev-live-reload')
|
||||
@ -109,16 +111,18 @@ describe('Dev Live Reload', () => {
|
||||
it('removes its commands', async () => {
|
||||
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
|
||||
await atom.packages.activatePackage('dev-live-reload')
|
||||
expect(atom.commands
|
||||
.findCommands({target: atom.views.getView(atom.workspace)})
|
||||
.filter(command => command.name.startsWith('dev-live-reload'))
|
||||
.length).toBeGreaterThan(0)
|
||||
expect(
|
||||
atom.commands
|
||||
.findCommands({ target: atom.views.getView(atom.workspace) })
|
||||
.filter(command => command.name.startsWith('dev-live-reload')).length
|
||||
).toBeGreaterThan(0)
|
||||
|
||||
await atom.packages.deactivatePackage('dev-live-reload')
|
||||
expect(atom.commands
|
||||
.findCommands({target: atom.views.getView(atom.workspace)})
|
||||
.filter(command => command.name.startsWith('dev-live-reload'))
|
||||
.length).toBe(0)
|
||||
expect(
|
||||
atom.commands
|
||||
.findCommands({ target: atom.views.getView(atom.workspace) })
|
||||
.filter(command => command.name.startsWith('dev-live-reload')).length
|
||||
).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -2,25 +2,31 @@ const path = require('path')
|
||||
|
||||
const UIWatcher = require('../lib/ui-watcher')
|
||||
|
||||
const {it, fit, ffit, afterEach, beforeEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
const { conditionPromise } = require('./async-spec-helpers')
|
||||
|
||||
describe('UIWatcher', () => {
|
||||
let uiWatcher = null
|
||||
|
||||
beforeEach(() => atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures')))
|
||||
beforeEach(() =>
|
||||
atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures'))
|
||||
)
|
||||
|
||||
afterEach(() => uiWatcher && uiWatcher.destroy())
|
||||
|
||||
describe("when a base theme's file changes", () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom.themes, 'resolveStylesheet').andReturn(path.join(__dirname, 'fixtures', 'static', 'atom.less'))
|
||||
spyOn(atom.themes, 'resolveStylesheet').andReturn(
|
||||
path.join(__dirname, 'fixtures', 'static', 'atom.less')
|
||||
)
|
||||
uiWatcher = new UIWatcher()
|
||||
})
|
||||
|
||||
it('reloads all the base styles', () => {
|
||||
spyOn(atom.themes, 'reloadBaseStylesheets')
|
||||
|
||||
expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(`${path.sep}static${path.sep}`)
|
||||
expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(
|
||||
`${path.sep}static${path.sep}`
|
||||
)
|
||||
|
||||
uiWatcher.baseTheme.entities[0].emitter.emit('did-change')
|
||||
expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled()
|
||||
@ -28,7 +34,11 @@ describe('UIWatcher', () => {
|
||||
})
|
||||
|
||||
it("watches all the style sheets in the theme's styles folder", async () => {
|
||||
const packagePath = path.join(__dirname, 'fixtures', 'package-with-styles-folder')
|
||||
const packagePath = path.join(
|
||||
__dirname,
|
||||
'fixtures',
|
||||
'package-with-styles-folder'
|
||||
)
|
||||
|
||||
await atom.packages.activatePackage(packagePath)
|
||||
uiWatcher = new UIWatcher()
|
||||
@ -36,15 +46,25 @@ describe('UIWatcher', () => {
|
||||
const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1]
|
||||
|
||||
expect(lastWatcher.entities.length).toBe(4)
|
||||
expect(lastWatcher.entities[0].getPath()).toBe(path.join(packagePath, 'styles'))
|
||||
expect(lastWatcher.entities[1].getPath()).toBe(path.join(packagePath, 'styles', '3.css'))
|
||||
expect(lastWatcher.entities[2].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '1.css'))
|
||||
expect(lastWatcher.entities[3].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '2.less'))
|
||||
expect(lastWatcher.entities[0].getPath()).toBe(
|
||||
path.join(packagePath, 'styles')
|
||||
)
|
||||
expect(lastWatcher.entities[1].getPath()).toBe(
|
||||
path.join(packagePath, 'styles', '3.css')
|
||||
)
|
||||
expect(lastWatcher.entities[2].getPath()).toBe(
|
||||
path.join(packagePath, 'styles', 'sub', '1.css')
|
||||
)
|
||||
expect(lastWatcher.entities[3].getPath()).toBe(
|
||||
path.join(packagePath, 'styles', 'sub', '2.less')
|
||||
)
|
||||
})
|
||||
|
||||
describe('when a package stylesheet file changes', async () => {
|
||||
beforeEach(async () => {
|
||||
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-manifest'))
|
||||
await atom.packages.activatePackage(
|
||||
path.join(__dirname, 'fixtures', 'package-with-styles-manifest')
|
||||
)
|
||||
uiWatcher = new UIWatcher()
|
||||
})
|
||||
|
||||
@ -71,7 +91,10 @@ describe('UIWatcher', () => {
|
||||
|
||||
describe('when a package global file changes', () => {
|
||||
beforeEach(async () => {
|
||||
atom.config.set('core.themes', ['theme-with-ui-variables', 'theme-with-multiple-imported-files'])
|
||||
atom.config.set('core.themes', [
|
||||
'theme-with-ui-variables',
|
||||
'theme-with-multiple-imported-files'
|
||||
])
|
||||
|
||||
await atom.themes.activateThemes()
|
||||
uiWatcher = new UIWatcher()
|
||||
@ -85,7 +108,9 @@ describe('UIWatcher', () => {
|
||||
spyOn(theme, 'reloadStylesheets')
|
||||
}
|
||||
|
||||
for (const entity of uiWatcher.watchedThemes.get('theme-with-multiple-imported-files').entities) {
|
||||
for (const entity of uiWatcher.watchedThemes.get(
|
||||
'theme-with-multiple-imported-files'
|
||||
).entities) {
|
||||
if (entity.getPath().indexOf('variables') > -1) varEntity = entity
|
||||
}
|
||||
varEntity.emitter.emit('did-change')
|
||||
@ -101,21 +126,31 @@ describe('UIWatcher', () => {
|
||||
uiWatcher = new UIWatcher()
|
||||
expect(uiWatcher.watchedPackages.size).toBe(0)
|
||||
|
||||
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder'))
|
||||
expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).not.toBeUndefined()
|
||||
await atom.packages.activatePackage(
|
||||
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
|
||||
)
|
||||
expect(
|
||||
uiWatcher.watchedPackages.get('package-with-styles-folder')
|
||||
).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it('unwatches a package after it is deactivated', async () => {
|
||||
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder'))
|
||||
await atom.packages.activatePackage(
|
||||
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
|
||||
)
|
||||
uiWatcher = new UIWatcher()
|
||||
const watcher = uiWatcher.watchedPackages.get('package-with-styles-folder')
|
||||
const watcher = uiWatcher.watchedPackages.get(
|
||||
'package-with-styles-folder'
|
||||
)
|
||||
expect(watcher).not.toBeUndefined()
|
||||
|
||||
const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy')
|
||||
watcher.onDidDestroy(watcherDestructionSpy)
|
||||
|
||||
await atom.packages.deactivatePackage('package-with-styles-folder')
|
||||
expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).toBeUndefined()
|
||||
expect(
|
||||
uiWatcher.watchedPackages.get('package-with-styles-folder')
|
||||
).toBeUndefined()
|
||||
expect(uiWatcher.watchedPackages.size).toBe(0)
|
||||
expect(watcherDestructionSpy).toHaveBeenCalled()
|
||||
})
|
||||
@ -124,7 +159,9 @@ describe('UIWatcher', () => {
|
||||
uiWatcher = new UIWatcher()
|
||||
uiWatcher.destroy()
|
||||
|
||||
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder'))
|
||||
await atom.packages.activatePackage(
|
||||
path.join(__dirname, 'fixtures', 'package-with-styles-folder')
|
||||
)
|
||||
expect(uiWatcher.watchedPackages.size).toBe(0)
|
||||
})
|
||||
})
|
||||
@ -132,7 +169,10 @@ describe('UIWatcher', () => {
|
||||
describe('minimal theme packages', () => {
|
||||
let pack = null
|
||||
beforeEach(async () => {
|
||||
atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-index-less'])
|
||||
atom.config.set('core.themes', [
|
||||
'theme-with-syntax-variables',
|
||||
'theme-with-index-less'
|
||||
])
|
||||
await atom.themes.activateThemes()
|
||||
uiWatcher = new UIWatcher()
|
||||
pack = atom.themes.getActiveThemes()[0]
|
||||
@ -157,7 +197,10 @@ describe('UIWatcher', () => {
|
||||
describe('theme packages', () => {
|
||||
let pack = null
|
||||
beforeEach(async () => {
|
||||
atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-multiple-imported-files'])
|
||||
atom.config.set('core.themes', [
|
||||
'theme-with-syntax-variables',
|
||||
'theme-with-multiple-imported-files'
|
||||
])
|
||||
|
||||
await atom.themes.activateThemes()
|
||||
uiWatcher = new UIWatcher()
|
||||
@ -170,7 +213,9 @@ describe('UIWatcher', () => {
|
||||
spyOn(pack, 'reloadStylesheets')
|
||||
spyOn(atom.themes, 'reloadBaseStylesheets')
|
||||
|
||||
const watcher = uiWatcher.watchedThemes.get('theme-with-multiple-imported-files')
|
||||
const watcher = uiWatcher.watchedThemes.get(
|
||||
'theme-with-multiple-imported-files'
|
||||
)
|
||||
|
||||
expect(watcher.entities.length).toBe(6)
|
||||
|
||||
@ -186,14 +231,21 @@ describe('UIWatcher', () => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
atom.config.set('core.themes', [])
|
||||
await conditionPromise(() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files'])
|
||||
await conditionPromise(
|
||||
() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files']
|
||||
)
|
||||
})
|
||||
|
||||
it('watches a new theme when it is deactivated', async () => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-package-file'])
|
||||
await conditionPromise(() => uiWatcher.watchedThemes.get('theme-with-package-file'))
|
||||
atom.config.set('core.themes', [
|
||||
'theme-with-syntax-variables',
|
||||
'theme-with-package-file'
|
||||
])
|
||||
await conditionPromise(() =>
|
||||
uiWatcher.watchedThemes.get('theme-with-package-file')
|
||||
)
|
||||
|
||||
pack = atom.themes.getActiveThemes()[0]
|
||||
spyOn(pack, 'reloadStylesheets')
|
||||
|
@ -1,6 +1,6 @@
|
||||
/** @babel */
|
||||
|
||||
import {CompositeDisposable} from 'atom'
|
||||
import { CompositeDisposable } from 'atom'
|
||||
|
||||
let reporter
|
||||
|
||||
@ -13,36 +13,44 @@ function getReporter () {
|
||||
}
|
||||
|
||||
export default {
|
||||
activate() {
|
||||
activate () {
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
|
||||
if (!atom.config.get('exception-reporting.userId')) {
|
||||
atom.config.set('exception-reporting.userId', require('node-uuid').v4())
|
||||
}
|
||||
|
||||
this.subscriptions.add(atom.onDidThrowError(({message, url, line, column, originalError}) => {
|
||||
try {
|
||||
getReporter().reportUncaughtException(originalError)
|
||||
} catch (secondaryException) {
|
||||
this.subscriptions.add(
|
||||
atom.onDidThrowError(({ message, url, line, column, originalError }) => {
|
||||
try {
|
||||
console.error("Error reporting uncaught exception", secondaryException)
|
||||
getReporter().reportUncaughtException(secondaryException)
|
||||
} catch (error) { }
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (atom.onDidFailAssertion != null) {
|
||||
this.subscriptions.add(atom.onDidFailAssertion(error => {
|
||||
try {
|
||||
getReporter().reportFailedAssertion(error)
|
||||
getReporter().reportUncaughtException(originalError)
|
||||
} catch (secondaryException) {
|
||||
try {
|
||||
console.error("Error reporting assertion failure", secondaryException)
|
||||
console.error(
|
||||
'Error reporting uncaught exception',
|
||||
secondaryException
|
||||
)
|
||||
getReporter().reportUncaughtException(secondaryException)
|
||||
} catch (error) {}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (atom.onDidFailAssertion != null) {
|
||||
this.subscriptions.add(
|
||||
atom.onDidFailAssertion(error => {
|
||||
try {
|
||||
getReporter().reportFailedAssertion(error)
|
||||
} catch (secondaryException) {
|
||||
try {
|
||||
console.error(
|
||||
'Error reporting assertion failure',
|
||||
secondaryException
|
||||
)
|
||||
getReporter().reportUncaughtException(secondaryException)
|
||||
} catch (error) {}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
/** @babel */
|
||||
|
||||
import _ from 'underscore-plus'
|
||||
import os from 'os'
|
||||
import stackTrace from 'stack-trace'
|
||||
import fs from 'fs-plus'
|
||||
@ -13,9 +12,15 @@ const StackTraceCache = new WeakMap()
|
||||
export default class Reporter {
|
||||
constructor (params = {}) {
|
||||
this.request = params.request || window.fetch
|
||||
this.alwaysReport = params.hasOwnProperty('alwaysReport') ? params.alwaysReport : false
|
||||
this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors') ? params.reportPreviousErrors : true
|
||||
this.resourcePath = this.normalizePath(params.resourcePath || process.resourcesPath)
|
||||
this.alwaysReport = params.hasOwnProperty('alwaysReport')
|
||||
? params.alwaysReport
|
||||
: false
|
||||
this.reportPreviousErrors = params.hasOwnProperty('reportPreviousErrors')
|
||||
? params.reportPreviousErrors
|
||||
: true
|
||||
this.resourcePath = this.normalizePath(
|
||||
params.resourcePath || process.resourcesPath
|
||||
)
|
||||
this.reportedErrors = []
|
||||
this.reportedAssertionFailures = []
|
||||
}
|
||||
@ -28,22 +33,24 @@ export default class Reporter {
|
||||
version: LIB_VERSION,
|
||||
url: 'https://www.atom.io'
|
||||
},
|
||||
events: [{
|
||||
payloadVersion: "2",
|
||||
exceptions: [this.buildExceptionJSON(error, params.projectRoot)],
|
||||
severity: params.severity,
|
||||
user: {
|
||||
id: params.userId
|
||||
},
|
||||
app: {
|
||||
version: params.appVersion,
|
||||
releaseStage: params.releaseStage
|
||||
},
|
||||
device: {
|
||||
osVersion: params.osVersion
|
||||
},
|
||||
metaData: error.metadata
|
||||
}]
|
||||
events: [
|
||||
{
|
||||
payloadVersion: '2',
|
||||
exceptions: [this.buildExceptionJSON(error, params.projectRoot)],
|
||||
severity: params.severity,
|
||||
user: {
|
||||
id: params.userId
|
||||
},
|
||||
app: {
|
||||
version: params.appVersion,
|
||||
releaseStage: params.releaseStage
|
||||
},
|
||||
device: {
|
||||
osVersion: params.osVersion
|
||||
},
|
||||
metaData: error.metadata
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +66,8 @@ export default class Reporter {
|
||||
return this.parseStackTrace(error).map(callSite => {
|
||||
return {
|
||||
file: this.scrubPath(callSite.getFileName()),
|
||||
method: callSite.getMethodName() || callSite.getFunctionName() || "none",
|
||||
method:
|
||||
callSite.getMethodName() || callSite.getFunctionName() || 'none',
|
||||
lineNumber: callSite.getLineNumber(),
|
||||
columnNumber: callSite.getColumnNumber(),
|
||||
inProject: !/node_modules/.test(callSite.getFileName())
|
||||
@ -69,8 +77,8 @@ export default class Reporter {
|
||||
|
||||
normalizePath (pathToNormalize) {
|
||||
return pathToNormalize
|
||||
.replace('file:///', '') // Sometimes it's a uri
|
||||
.replace(/\\/g, '/') // Unify path separators across Win/macOS/Linux
|
||||
.replace('file:///', '') // Sometimes it's a uri
|
||||
.replace(/\\/g, '/') // Unify path separators across Win/macOS/Linux
|
||||
}
|
||||
|
||||
scrubPath (pathToScrub) {
|
||||
@ -96,17 +104,17 @@ export default class Reporter {
|
||||
}
|
||||
|
||||
getReleaseChannel (version) {
|
||||
return (version.indexOf('beta') > -1)
|
||||
return version.indexOf('beta') > -1
|
||||
? 'beta'
|
||||
: (version.indexOf('dev') > -1)
|
||||
? 'dev'
|
||||
: 'stable'
|
||||
: version.indexOf('dev') > -1
|
||||
? 'dev'
|
||||
: 'stable'
|
||||
}
|
||||
|
||||
performRequest (json) {
|
||||
this.request.call(null, 'https://notify.bugsnag.com', {
|
||||
method: 'POST',
|
||||
headers: new Headers({'Content-Type': 'application/json'}),
|
||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify(json)
|
||||
})
|
||||
}
|
||||
@ -118,7 +126,10 @@ export default class Reporter {
|
||||
|
||||
const topFrame = this.parseStackTrace(error)[0]
|
||||
const fileName = topFrame ? topFrame.getFileName() : null
|
||||
return fileName && (this.isBundledFile(fileName) || this.isTeletypeFile(fileName))
|
||||
return (
|
||||
fileName &&
|
||||
(this.isBundledFile(fileName) || this.isTeletypeFile(fileName))
|
||||
)
|
||||
}
|
||||
|
||||
parseStackTrace (error) {
|
||||
@ -169,21 +180,24 @@ export default class Reporter {
|
||||
|
||||
notification = atom.notifications.addInfo(message, {
|
||||
detail: error.privateMetadataDescription,
|
||||
description: "Are you willing to submit this information to a private server for debugging purposes?",
|
||||
description:
|
||||
'Are you willing to submit this information to a private server for debugging purposes?',
|
||||
dismissable: true,
|
||||
buttons: [
|
||||
{
|
||||
text: "No",
|
||||
text: 'No',
|
||||
onDidClick: reportWithoutPrivateMetadata
|
||||
},
|
||||
{
|
||||
text: "Yes, Submit for Debugging",
|
||||
text: 'Yes, Submit for Debugging',
|
||||
onDidClick: reportWithPrivateMetadata
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
dismissSubscription = notification.onDidDismiss(reportWithoutPrivateMetadata)
|
||||
dismissSubscription = notification.onDidDismiss(
|
||||
reportWithoutPrivateMetadata
|
||||
)
|
||||
}
|
||||
|
||||
addPackageMetadata (error) {
|
||||
@ -200,7 +214,9 @@ export default class Reporter {
|
||||
}
|
||||
}
|
||||
|
||||
if (error.metadata == null) { error.metadata = {} }
|
||||
if (error.metadata == null) {
|
||||
error.metadata = {}
|
||||
}
|
||||
error.metadata.bundledPackages = bundledPackages
|
||||
error.metadata.userPackages = userPackages
|
||||
}
|
||||
@ -209,8 +225,12 @@ export default class Reporter {
|
||||
addPreviousErrorsMetadata (error) {
|
||||
if (!this.reportPreviousErrors) return
|
||||
if (!error.metadata) error.metadata = {}
|
||||
error.metadata.previousErrors = this.reportedErrors.map(error => error.message)
|
||||
error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map(error => error.message)
|
||||
error.metadata.previousErrors = this.reportedErrors.map(
|
||||
error => error.message
|
||||
)
|
||||
error.metadata.previousAssertionFailures = this.reportedAssertionFailures.map(
|
||||
error => error.message
|
||||
)
|
||||
}
|
||||
|
||||
reportUncaughtException (error) {
|
||||
@ -219,13 +239,20 @@ export default class Reporter {
|
||||
this.addPackageMetadata(error)
|
||||
this.addPreviousErrorsMetadata(error)
|
||||
|
||||
if ((error.privateMetadata != null) && (error.privateMetadataDescription != null)) {
|
||||
this.requestPrivateMetadataConsent(error, "The Atom team would like to collect the following information to resolve this error:", error => this.reportUncaughtException(error))
|
||||
if (
|
||||
error.privateMetadata != null &&
|
||||
error.privateMetadataDescription != null
|
||||
) {
|
||||
this.requestPrivateMetadataConsent(
|
||||
error,
|
||||
'The Atom team would like to collect the following information to resolve this error:',
|
||||
error => this.reportUncaughtException(error)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let params = this.getDefaultNotificationParams()
|
||||
params.severity = "error"
|
||||
params.severity = 'error'
|
||||
this.performRequest(this.buildNotificationJSON(error, params))
|
||||
this.reportedErrors.push(error)
|
||||
}
|
||||
@ -236,13 +263,20 @@ export default class Reporter {
|
||||
this.addPackageMetadata(error)
|
||||
this.addPreviousErrorsMetadata(error)
|
||||
|
||||
if ((error.privateMetadata != null) && (error.privateMetadataDescription != null)) {
|
||||
this.requestPrivateMetadataConsent(error, "The Atom team would like to collect some information to resolve an unexpected condition:", error => this.reportFailedAssertion(error))
|
||||
if (
|
||||
error.privateMetadata != null &&
|
||||
error.privateMetadataDescription != null
|
||||
) {
|
||||
this.requestPrivateMetadataConsent(
|
||||
error,
|
||||
'The Atom team would like to collect some information to resolve an unexpected condition:',
|
||||
error => this.reportFailedAssertion(error)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let params = this.getDefaultNotificationParams()
|
||||
params.severity = "warning"
|
||||
params.severity = 'warning'
|
||||
this.performRequest(this.buildNotificationJSON(error, params))
|
||||
this.reportedAssertionFailures.push(error)
|
||||
}
|
||||
@ -258,10 +292,11 @@ export default class Reporter {
|
||||
|
||||
isTeletypeFile (fileName) {
|
||||
const teletypePath = atom.packages.resolvePackagePath('teletype')
|
||||
return teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0
|
||||
return (
|
||||
teletypePath && this.normalizePath(fileName).indexOf(teletypePath) === 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reporter.API_KEY = API_KEY
|
||||
Reporter.LIB_VERSION = LIB_VERSION
|
||||
|
@ -6,25 +6,31 @@ const fs = require('fs-plus')
|
||||
let osVersion = `${os.platform()}-${os.arch()}-${os.release()}`
|
||||
|
||||
let getReleaseChannel = version => {
|
||||
return (version.indexOf('beta') > -1)
|
||||
return version.indexOf('beta') > -1
|
||||
? 'beta'
|
||||
: (version.indexOf('dev') > -1)
|
||||
: version.indexOf('dev') > -1
|
||||
? 'dev'
|
||||
: 'stable'
|
||||
}
|
||||
|
||||
describe("Reporter", () => {
|
||||
let reporter, requests, initialStackTraceLimit, initialFsGetHomeDirectory, mockActivePackages
|
||||
describe('Reporter', () => {
|
||||
let reporter,
|
||||
requests,
|
||||
initialStackTraceLimit,
|
||||
initialFsGetHomeDirectory,
|
||||
mockActivePackages
|
||||
|
||||
beforeEach(() => {
|
||||
reporter = new Reporter({
|
||||
request: (url, options) => requests.push(Object.assign({url}, options)),
|
||||
request: (url, options) => requests.push(Object.assign({ url }, options)),
|
||||
alwaysReport: true,
|
||||
reportPreviousErrors: false
|
||||
})
|
||||
requests = []
|
||||
mockActivePackages = []
|
||||
spyOn(atom.packages, 'getActivePackages').andCallFake(() => mockActivePackages)
|
||||
spyOn(atom.packages, 'getActivePackages').andCallFake(
|
||||
() => mockActivePackages
|
||||
)
|
||||
|
||||
initialStackTraceLimit = Error.stackTraceLimit
|
||||
Error.stackTraceLimit = 1
|
||||
@ -37,11 +43,12 @@ describe("Reporter", () => {
|
||||
Error.stackTraceLimit = initialStackTraceLimit
|
||||
})
|
||||
|
||||
describe(".reportUncaughtException(error)", () => {
|
||||
it("posts errors originated inside Atom Core to BugSnag", () => {
|
||||
describe('.reportUncaughtException(error)', () => {
|
||||
it('posts errors originated inside Atom Core to BugSnag', () => {
|
||||
const repositoryRootPath = path.join(__dirname, '..')
|
||||
reporter = new Reporter({
|
||||
request: (url, options) => requests.push(Object.assign({url}, options)),
|
||||
request: (url, options) =>
|
||||
requests.push(Object.assign({ url }, options)),
|
||||
alwaysReport: true,
|
||||
reportPreviousErrors: false,
|
||||
resourcePath: repositoryRootPath
|
||||
@ -50,111 +57,123 @@ describe("Reporter", () => {
|
||||
let error = new Error()
|
||||
Error.captureStackTrace(error)
|
||||
reporter.reportUncaughtException(error)
|
||||
let [lineNumber, columnNumber] = error.stack.match(/.js:(\d+):(\d+)/).slice(1).map(s => parseInt(s))
|
||||
let [lineNumber, columnNumber] = error.stack
|
||||
.match(/.js:(\d+):(\d+)/)
|
||||
.slice(1)
|
||||
.map(s => parseInt(s))
|
||||
|
||||
expect(requests.length).toBe(1)
|
||||
let [request] = requests
|
||||
expect(request.method).toBe("POST")
|
||||
expect(request.url).toBe("https://notify.bugsnag.com")
|
||||
expect(request.headers.get("Content-Type")).toBe("application/json")
|
||||
expect(request.method).toBe('POST')
|
||||
expect(request.url).toBe('https://notify.bugsnag.com')
|
||||
expect(request.headers.get('Content-Type')).toBe('application/json')
|
||||
let body = JSON.parse(request.body)
|
||||
// Delete `inProject` field because tests may fail when run as part of Atom core
|
||||
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
|
||||
delete body.events[0].exceptions[0].stacktrace[0].inProject
|
||||
|
||||
expect(body).toEqual({
|
||||
"apiKey": Reporter.API_KEY,
|
||||
"notifier": {
|
||||
"name": "Atom",
|
||||
"version": Reporter.LIB_VERSION,
|
||||
"url": "https://www.atom.io"
|
||||
apiKey: Reporter.API_KEY,
|
||||
notifier: {
|
||||
name: 'Atom',
|
||||
version: Reporter.LIB_VERSION,
|
||||
url: 'https://www.atom.io'
|
||||
},
|
||||
"events": [
|
||||
events: [
|
||||
{
|
||||
"payloadVersion": "2",
|
||||
"exceptions": [
|
||||
payloadVersion: '2',
|
||||
exceptions: [
|
||||
{
|
||||
"errorClass": "Error",
|
||||
"message": "",
|
||||
"stacktrace": [
|
||||
errorClass: 'Error',
|
||||
message: '',
|
||||
stacktrace: [
|
||||
{
|
||||
"method": semver.gt(process.versions.electron, '1.6.0') ? 'Spec.it' : 'it',
|
||||
"file": "spec/reporter-spec.js",
|
||||
"lineNumber": lineNumber,
|
||||
"columnNumber": columnNumber
|
||||
method: semver.gt(process.versions.electron, '1.6.0')
|
||||
? 'Spec.it'
|
||||
: 'it',
|
||||
file: 'spec/reporter-spec.js',
|
||||
lineNumber: lineNumber,
|
||||
columnNumber: columnNumber
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"user": {},
|
||||
"app": {
|
||||
"version": atom.getVersion(),
|
||||
"releaseStage": getReleaseChannel(atom.getVersion())
|
||||
severity: 'error',
|
||||
user: {},
|
||||
app: {
|
||||
version: atom.getVersion(),
|
||||
releaseStage: getReleaseChannel(atom.getVersion())
|
||||
},
|
||||
"device": {
|
||||
"osVersion": osVersion
|
||||
device: {
|
||||
osVersion: osVersion
|
||||
}
|
||||
}
|
||||
]
|
||||
});})
|
||||
})
|
||||
})
|
||||
|
||||
it("posts errors originated outside Atom Core to BugSnag", () => {
|
||||
it('posts errors originated outside Atom Core to BugSnag', () => {
|
||||
fs.getHomeDirectory = () => path.join(__dirname, '..', '..')
|
||||
|
||||
let error = new Error()
|
||||
Error.captureStackTrace(error)
|
||||
reporter.reportUncaughtException(error)
|
||||
let [lineNumber, columnNumber] = error.stack.match(/.js:(\d+):(\d+)/).slice(1).map(s => parseInt(s))
|
||||
let [lineNumber, columnNumber] = error.stack
|
||||
.match(/.js:(\d+):(\d+)/)
|
||||
.slice(1)
|
||||
.map(s => parseInt(s))
|
||||
|
||||
expect(requests.length).toBe(1)
|
||||
let [request] = requests
|
||||
expect(request.method).toBe("POST")
|
||||
expect(request.url).toBe("https://notify.bugsnag.com")
|
||||
expect(request.headers.get("Content-Type")).toBe("application/json")
|
||||
expect(request.method).toBe('POST')
|
||||
expect(request.url).toBe('https://notify.bugsnag.com')
|
||||
expect(request.headers.get('Content-Type')).toBe('application/json')
|
||||
let body = JSON.parse(request.body)
|
||||
// Delete `inProject` field because tests may fail when run as part of Atom core
|
||||
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
|
||||
delete body.events[0].exceptions[0].stacktrace[0].inProject
|
||||
|
||||
expect(body).toEqual({
|
||||
"apiKey": Reporter.API_KEY,
|
||||
"notifier": {
|
||||
"name": "Atom",
|
||||
"version": Reporter.LIB_VERSION,
|
||||
"url": "https://www.atom.io"
|
||||
apiKey: Reporter.API_KEY,
|
||||
notifier: {
|
||||
name: 'Atom',
|
||||
version: Reporter.LIB_VERSION,
|
||||
url: 'https://www.atom.io'
|
||||
},
|
||||
"events": [
|
||||
events: [
|
||||
{
|
||||
"payloadVersion": "2",
|
||||
"exceptions": [
|
||||
payloadVersion: '2',
|
||||
exceptions: [
|
||||
{
|
||||
"errorClass": "Error",
|
||||
"message": "",
|
||||
"stacktrace": [
|
||||
errorClass: 'Error',
|
||||
message: '',
|
||||
stacktrace: [
|
||||
{
|
||||
"method": semver.gt(process.versions.electron, '1.6.0') ? 'Spec.it' : 'it',
|
||||
"file": '~/exception-reporting/spec/reporter-spec.js',
|
||||
"lineNumber": lineNumber,
|
||||
"columnNumber": columnNumber
|
||||
method: semver.gt(process.versions.electron, '1.6.0')
|
||||
? 'Spec.it'
|
||||
: 'it',
|
||||
file: '~/exception-reporting/spec/reporter-spec.js',
|
||||
lineNumber: lineNumber,
|
||||
columnNumber: columnNumber
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"severity": "error",
|
||||
"user": {},
|
||||
"app": {
|
||||
"version": atom.getVersion(),
|
||||
"releaseStage": getReleaseChannel(atom.getVersion())
|
||||
severity: 'error',
|
||||
user: {},
|
||||
app: {
|
||||
version: atom.getVersion(),
|
||||
releaseStage: getReleaseChannel(atom.getVersion())
|
||||
},
|
||||
"device": {
|
||||
"osVersion": osVersion
|
||||
device: {
|
||||
osVersion: osVersion
|
||||
}
|
||||
}
|
||||
]
|
||||
});})
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the error object has `privateMetadata` and `privateMetadataDescription` fields", () => {
|
||||
describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => {
|
||||
let [error, notification] = []
|
||||
|
||||
beforeEach(() => {
|
||||
@ -164,17 +183,17 @@ describe("Reporter", () => {
|
||||
error = new Error()
|
||||
Error.captureStackTrace(error)
|
||||
|
||||
error.metadata = {foo: "bar"}
|
||||
error.privateMetadata = {baz: "quux"}
|
||||
error.privateMetadataDescription = "The contents of baz"
|
||||
error.metadata = { foo: 'bar' }
|
||||
error.privateMetadata = { baz: 'quux' }
|
||||
error.privateMetadataDescription = 'The contents of baz'
|
||||
})
|
||||
|
||||
it("posts a notification asking for consent", () => {
|
||||
it('posts a notification asking for consent', () => {
|
||||
reporter.reportUncaughtException(error)
|
||||
expect(atom.notifications.addInfo).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("submits the error with the private metadata if the user consents", () => {
|
||||
it('submits the error with the private metadata if the user consents', () => {
|
||||
spyOn(reporter, 'reportUncaughtException').andCallThrough()
|
||||
reporter.reportUncaughtException(error)
|
||||
reporter.reportUncaughtException.reset()
|
||||
@ -189,12 +208,12 @@ describe("Reporter", () => {
|
||||
expect(reporter.reportUncaughtException.callCount).toBe(1)
|
||||
expect(error.privateMetadata).toBeUndefined()
|
||||
expect(error.privateMetadataDescription).toBeUndefined()
|
||||
expect(error.metadata).toEqual({foo: "bar", baz: "quux"})
|
||||
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' })
|
||||
|
||||
expect(notification.isDismissed()).toBe(true)
|
||||
})
|
||||
|
||||
it("submits the error without the private metadata if the user does not consent", () => {
|
||||
it('submits the error without the private metadata if the user does not consent', () => {
|
||||
spyOn(reporter, 'reportUncaughtException').andCallThrough()
|
||||
reporter.reportUncaughtException(error)
|
||||
reporter.reportUncaughtException.reset()
|
||||
@ -209,12 +228,12 @@ describe("Reporter", () => {
|
||||
expect(reporter.reportUncaughtException.callCount).toBe(1)
|
||||
expect(error.privateMetadata).toBeUndefined()
|
||||
expect(error.privateMetadataDescription).toBeUndefined()
|
||||
expect(error.metadata).toEqual({foo: "bar"})
|
||||
expect(error.metadata).toEqual({ foo: 'bar' })
|
||||
|
||||
expect(notification.isDismissed()).toBe(true)
|
||||
})
|
||||
|
||||
it("submits the error without the private metadata if the user dismisses the notification", () => {
|
||||
it('submits the error without the private metadata if the user dismisses the notification', () => {
|
||||
spyOn(reporter, 'reportUncaughtException').andCallThrough()
|
||||
reporter.reportUncaughtException(error)
|
||||
reporter.reportUncaughtException.reset()
|
||||
@ -226,14 +245,34 @@ describe("Reporter", () => {
|
||||
expect(reporter.reportUncaughtException.callCount).toBe(1)
|
||||
expect(error.privateMetadata).toBeUndefined()
|
||||
expect(error.privateMetadataDescription).toBeUndefined()
|
||||
expect(error.metadata).toEqual({foo: "bar"});});})
|
||||
expect(error.metadata).toEqual({ foo: 'bar' })
|
||||
})
|
||||
})
|
||||
|
||||
it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => {
|
||||
mockActivePackages = [
|
||||
{name: 'user-1', path: '/Users/user/.atom/packages/user-1', metadata: {version: '1.0.0'}},
|
||||
{name: 'user-2', path: '/Users/user/.atom/packages/user-2', metadata: {version: '1.2.0'}},
|
||||
{name: 'bundled-1', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1', metadata: {version: '1.0.0'}},
|
||||
{name: 'bundled-2', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2', metadata: {version: '1.2.0'}},
|
||||
{
|
||||
name: 'user-1',
|
||||
path: '/Users/user/.atom/packages/user-1',
|
||||
metadata: { version: '1.0.0' }
|
||||
},
|
||||
{
|
||||
name: 'user-2',
|
||||
path: '/Users/user/.atom/packages/user-2',
|
||||
metadata: { version: '1.2.0' }
|
||||
},
|
||||
{
|
||||
name: 'bundled-1',
|
||||
path:
|
||||
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1',
|
||||
metadata: { version: '1.0.0' }
|
||||
},
|
||||
{
|
||||
name: 'bundled-2',
|
||||
path:
|
||||
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2',
|
||||
metadata: { version: '1.2.0' }
|
||||
}
|
||||
]
|
||||
|
||||
const packageDirPaths = ['/Users/user/.atom/packages']
|
||||
@ -269,69 +308,78 @@ describe("Reporter", () => {
|
||||
const lastRequest = requests[requests.length - 1]
|
||||
const body = JSON.parse(lastRequest.body)
|
||||
|
||||
console.log(body);
|
||||
console.log(body)
|
||||
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B'])
|
||||
expect(body.events[0].metaData.previousAssertionFailures).toEqual(['X', 'Y'])
|
||||
expect(body.events[0].metaData.previousAssertionFailures).toEqual([
|
||||
'X',
|
||||
'Y'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe(".reportFailedAssertion(error)", () => {
|
||||
it("posts warnings to bugsnag", () => {
|
||||
describe('.reportFailedAssertion(error)', () => {
|
||||
it('posts warnings to bugsnag', () => {
|
||||
fs.getHomeDirectory = () => path.join(__dirname, '..', '..')
|
||||
|
||||
let error = new Error()
|
||||
Error.captureStackTrace(error)
|
||||
reporter.reportFailedAssertion(error)
|
||||
let [lineNumber, columnNumber] = error.stack.match(/.js:(\d+):(\d+)/).slice(1).map(s => parseInt(s))
|
||||
let [lineNumber, columnNumber] = error.stack
|
||||
.match(/.js:(\d+):(\d+)/)
|
||||
.slice(1)
|
||||
.map(s => parseInt(s))
|
||||
|
||||
expect(requests.length).toBe(1)
|
||||
let [request] = requests
|
||||
expect(request.method).toBe("POST")
|
||||
expect(request.url).toBe("https://notify.bugsnag.com")
|
||||
expect(request.headers.get("Content-Type")).toBe("application/json")
|
||||
expect(request.method).toBe('POST')
|
||||
expect(request.url).toBe('https://notify.bugsnag.com')
|
||||
expect(request.headers.get('Content-Type')).toBe('application/json')
|
||||
let body = JSON.parse(request.body)
|
||||
// Delete `inProject` field because tests may fail when run as part of Atom core
|
||||
// (i.e. when this test file will be located under `node_modules/exception-reporting/spec`)
|
||||
delete body.events[0].exceptions[0].stacktrace[0].inProject
|
||||
|
||||
expect(body).toEqual({
|
||||
"apiKey": Reporter.API_KEY,
|
||||
"notifier": {
|
||||
"name": "Atom",
|
||||
"version": Reporter.LIB_VERSION,
|
||||
"url": "https://www.atom.io"
|
||||
apiKey: Reporter.API_KEY,
|
||||
notifier: {
|
||||
name: 'Atom',
|
||||
version: Reporter.LIB_VERSION,
|
||||
url: 'https://www.atom.io'
|
||||
},
|
||||
"events": [
|
||||
events: [
|
||||
{
|
||||
"payloadVersion": "2",
|
||||
"exceptions": [
|
||||
payloadVersion: '2',
|
||||
exceptions: [
|
||||
{
|
||||
"errorClass": "Error",
|
||||
"message": "",
|
||||
"stacktrace": [
|
||||
errorClass: 'Error',
|
||||
message: '',
|
||||
stacktrace: [
|
||||
{
|
||||
"method": semver.gt(process.versions.electron, '1.6.0') ? 'Spec.it' : 'it',
|
||||
"file": '~/exception-reporting/spec/reporter-spec.js',
|
||||
"lineNumber": lineNumber,
|
||||
"columnNumber": columnNumber
|
||||
method: semver.gt(process.versions.electron, '1.6.0')
|
||||
? 'Spec.it'
|
||||
: 'it',
|
||||
file: '~/exception-reporting/spec/reporter-spec.js',
|
||||
lineNumber: lineNumber,
|
||||
columnNumber: columnNumber
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"severity": "warning",
|
||||
"user": {},
|
||||
"app": {
|
||||
"version": atom.getVersion(),
|
||||
"releaseStage": getReleaseChannel(atom.getVersion())
|
||||
severity: 'warning',
|
||||
user: {},
|
||||
app: {
|
||||
version: atom.getVersion(),
|
||||
releaseStage: getReleaseChannel(atom.getVersion())
|
||||
},
|
||||
"device": {
|
||||
"osVersion": osVersion
|
||||
device: {
|
||||
osVersion: osVersion
|
||||
}
|
||||
}
|
||||
]
|
||||
});})
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the error object has `privateMetadata` and `privateMetadataDescription` fields", () => {
|
||||
describe('when the error object has `privateMetadata` and `privateMetadataDescription` fields', () => {
|
||||
let [error, notification] = []
|
||||
|
||||
beforeEach(() => {
|
||||
@ -341,17 +389,17 @@ describe("Reporter", () => {
|
||||
error = new Error()
|
||||
Error.captureStackTrace(error)
|
||||
|
||||
error.metadata = {foo: "bar"}
|
||||
error.privateMetadata = {baz: "quux"}
|
||||
error.privateMetadataDescription = "The contents of baz"
|
||||
error.metadata = { foo: 'bar' }
|
||||
error.privateMetadata = { baz: 'quux' }
|
||||
error.privateMetadataDescription = 'The contents of baz'
|
||||
})
|
||||
|
||||
it("posts a notification asking for consent", () => {
|
||||
it('posts a notification asking for consent', () => {
|
||||
reporter.reportFailedAssertion(error)
|
||||
expect(atom.notifications.addInfo).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("submits the error with the private metadata if the user consents", () => {
|
||||
it('submits the error with the private metadata if the user consents', () => {
|
||||
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
|
||||
reporter.reportFailedAssertion(error)
|
||||
reporter.reportFailedAssertion.reset()
|
||||
@ -366,12 +414,12 @@ describe("Reporter", () => {
|
||||
expect(reporter.reportFailedAssertion.callCount).toBe(1)
|
||||
expect(error.privateMetadata).toBeUndefined()
|
||||
expect(error.privateMetadataDescription).toBeUndefined()
|
||||
expect(error.metadata).toEqual({foo: "bar", baz: "quux"})
|
||||
expect(error.metadata).toEqual({ foo: 'bar', baz: 'quux' })
|
||||
|
||||
expect(notification.isDismissed()).toBe(true)
|
||||
})
|
||||
|
||||
it("submits the error without the private metadata if the user does not consent", () => {
|
||||
it('submits the error without the private metadata if the user does not consent', () => {
|
||||
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
|
||||
reporter.reportFailedAssertion(error)
|
||||
reporter.reportFailedAssertion.reset()
|
||||
@ -386,12 +434,12 @@ describe("Reporter", () => {
|
||||
expect(reporter.reportFailedAssertion.callCount).toBe(1)
|
||||
expect(error.privateMetadata).toBeUndefined()
|
||||
expect(error.privateMetadataDescription).toBeUndefined()
|
||||
expect(error.metadata).toEqual({foo: "bar"})
|
||||
expect(error.metadata).toEqual({ foo: 'bar' })
|
||||
|
||||
expect(notification.isDismissed()).toBe(true)
|
||||
})
|
||||
|
||||
it("submits the error without the private metadata if the user dismisses the notification", () => {
|
||||
it('submits the error without the private metadata if the user dismisses the notification', () => {
|
||||
spyOn(reporter, 'reportFailedAssertion').andCallThrough()
|
||||
reporter.reportFailedAssertion(error)
|
||||
reporter.reportFailedAssertion.reset()
|
||||
@ -403,13 +451,17 @@ describe("Reporter", () => {
|
||||
expect(reporter.reportFailedAssertion.callCount).toBe(1)
|
||||
expect(error.privateMetadata).toBeUndefined()
|
||||
expect(error.privateMetadataDescription).toBeUndefined()
|
||||
expect(error.metadata).toEqual({foo: "bar"})
|
||||
expect(error.metadata).toEqual({ foo: 'bar' })
|
||||
})
|
||||
|
||||
it("only notifies the user once for a given 'privateMetadataRequestName'", () => {
|
||||
let fakeStorage = {}
|
||||
spyOn(global.localStorage, 'setItem').andCallFake((key, value) => fakeStorage[key] = value)
|
||||
spyOn(global.localStorage, 'getItem').andCallFake(key => fakeStorage[key])
|
||||
spyOn(global.localStorage, 'setItem').andCallFake(
|
||||
(key, value) => (fakeStorage[key] = value)
|
||||
)
|
||||
spyOn(global.localStorage, 'getItem').andCallFake(
|
||||
key => fakeStorage[key]
|
||||
)
|
||||
|
||||
error.privateMetadataRequestName = 'foo'
|
||||
|
||||
@ -423,7 +475,7 @@ describe("Reporter", () => {
|
||||
let error2 = new Error()
|
||||
Error.captureStackTrace(error2)
|
||||
error2.privateMetadataDescription = 'Something about you'
|
||||
error2.privateMetadata = {baz: 'quux'}
|
||||
error2.privateMetadata = { baz: 'quux' }
|
||||
error2.privateMetadataRequestName = 'bar'
|
||||
|
||||
reporter.reportFailedAssertion(error2)
|
||||
@ -433,10 +485,28 @@ describe("Reporter", () => {
|
||||
|
||||
it('treats packages located in atom.packages.getPackageDirPaths as user packages', () => {
|
||||
mockActivePackages = [
|
||||
{name: 'user-1', path: '/Users/user/.atom/packages/user-1', metadata: {version: '1.0.0'}},
|
||||
{name: 'user-2', path: '/Users/user/.atom/packages/user-2', metadata: {version: '1.2.0'}},
|
||||
{name: 'bundled-1', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1', metadata: {version: '1.0.0'}},
|
||||
{name: 'bundled-2', path: '/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2', metadata: {version: '1.2.0'}},
|
||||
{
|
||||
name: 'user-1',
|
||||
path: '/Users/user/.atom/packages/user-1',
|
||||
metadata: { version: '1.0.0' }
|
||||
},
|
||||
{
|
||||
name: 'user-2',
|
||||
path: '/Users/user/.atom/packages/user-2',
|
||||
metadata: { version: '1.2.0' }
|
||||
},
|
||||
{
|
||||
name: 'bundled-1',
|
||||
path:
|
||||
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-1',
|
||||
metadata: { version: '1.0.0' }
|
||||
},
|
||||
{
|
||||
name: 'bundled-2',
|
||||
path:
|
||||
'/Applications/Atom.app/Contents/Resources/app.asar/node_modules/bundled-2',
|
||||
metadata: { version: '1.2.0' }
|
||||
}
|
||||
]
|
||||
|
||||
const packageDirPaths = ['/Users/user/.atom/packages']
|
||||
@ -473,7 +543,10 @@ describe("Reporter", () => {
|
||||
const body = JSON.parse(lastRequest.body)
|
||||
|
||||
expect(body.events[0].metaData.previousErrors).toEqual(['A', 'B'])
|
||||
expect(body.events[0].metaData.previousAssertionFailures).toEqual(['X', 'Y'])
|
||||
expect(body.events[0].metaData.previousAssertionFailures).toEqual([
|
||||
'X',
|
||||
'Y'
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,13 @@
|
||||
const SelectListView = require('atom-select-list')
|
||||
const {repositoryForPath} = require('./helpers')
|
||||
const { repositoryForPath } = require('./helpers')
|
||||
|
||||
module.exports =
|
||||
class DiffListView {
|
||||
module.exports = class DiffListView {
|
||||
constructor () {
|
||||
this.selectListView = new SelectListView({
|
||||
emptyMessage: 'No diffs in file',
|
||||
items: [],
|
||||
filterKeyForItem: (diff) => diff.lineText,
|
||||
elementForItem: (diff) => {
|
||||
filterKeyForItem: diff => diff.lineText,
|
||||
elementForItem: diff => {
|
||||
const li = document.createElement('li')
|
||||
li.classList.add('two-lines')
|
||||
|
||||
@ -19,15 +18,19 @@ class DiffListView {
|
||||
|
||||
const secondaryLine = document.createElement('div')
|
||||
secondaryLine.classList.add('secondary-line')
|
||||
secondaryLine.textContent = `-${diff.oldStart},${diff.oldLines} +${diff.newStart},${diff.newLines}`
|
||||
secondaryLine.textContent = `-${diff.oldStart},${diff.oldLines} +${
|
||||
diff.newStart
|
||||
},${diff.newLines}`
|
||||
li.appendChild(secondaryLine)
|
||||
|
||||
return li
|
||||
},
|
||||
didConfirmSelection: (diff) => {
|
||||
didConfirmSelection: diff => {
|
||||
this.cancel()
|
||||
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart
|
||||
this.editor.setCursorBufferPosition([bufferRow, 0], {autoscroll: true})
|
||||
this.editor.setCursorBufferPosition([bufferRow, 0], {
|
||||
autoscroll: true
|
||||
})
|
||||
this.editor.moveToFirstCharacterOfLine()
|
||||
},
|
||||
didCancelSelection: () => {
|
||||
@ -35,7 +38,10 @@ class DiffListView {
|
||||
}
|
||||
})
|
||||
this.selectListView.element.classList.add('diff-list-view')
|
||||
this.panel = atom.workspace.addModalPanel({item: this.selectListView, visible: false})
|
||||
this.panel = atom.workspace.addModalPanel({
|
||||
item: this.selectListView,
|
||||
visible: false
|
||||
})
|
||||
}
|
||||
|
||||
attach () {
|
||||
@ -66,7 +72,9 @@ class DiffListView {
|
||||
} else if (editor) {
|
||||
this.editor = editor
|
||||
const repository = repositoryForPath(this.editor.getPath())
|
||||
let diffs = repository ? repository.getLineDiffs(this.editor.getPath(), this.editor.getText()) : []
|
||||
let diffs = repository
|
||||
? repository.getLineDiffs(this.editor.getPath(), this.editor.getText())
|
||||
: []
|
||||
if (!diffs) diffs = []
|
||||
for (let diff of diffs) {
|
||||
const bufferRow = diff.newStart > 0 ? diff.newStart - 1 : diff.newStart
|
||||
@ -74,7 +82,7 @@ class DiffListView {
|
||||
diff.lineText = lineText ? lineText.trim() : ''
|
||||
}
|
||||
|
||||
await this.selectListView.update({items: diffs})
|
||||
await this.selectListView.update({ items: diffs })
|
||||
this.attach()
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
const {CompositeDisposable} = require('atom')
|
||||
const {repositoryForPath} = require('./helpers')
|
||||
const { CompositeDisposable } = require('atom')
|
||||
const { repositoryForPath } = require('./helpers')
|
||||
|
||||
const MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024
|
||||
|
||||
module.exports =
|
||||
class GitDiffView {
|
||||
module.exports = class GitDiffView {
|
||||
constructor (editor) {
|
||||
this.updateDiffs = this.updateDiffs.bind(this)
|
||||
this.editor = editor
|
||||
@ -22,10 +21,18 @@ class GitDiffView {
|
||||
this.editor.onDidStopChanging(this.updateDiffs),
|
||||
this.editor.onDidChangePath(this.updateDiffs),
|
||||
atom.project.onDidChangePaths(() => this.subscribeToRepository()),
|
||||
atom.commands.add(editorElement, 'git-diff:move-to-next-diff', () => this.moveToNextDiff()),
|
||||
atom.commands.add(editorElement, 'git-diff:move-to-previous-diff', () => this.moveToPreviousDiff()),
|
||||
atom.config.onDidChange('git-diff.showIconsInEditorGutter', () => this.updateIconDecoration()),
|
||||
atom.config.onDidChange('editor.showLineNumbers', () => this.updateIconDecoration()),
|
||||
atom.commands.add(editorElement, 'git-diff:move-to-next-diff', () =>
|
||||
this.moveToNextDiff()
|
||||
),
|
||||
atom.commands.add(editorElement, 'git-diff:move-to-previous-diff', () =>
|
||||
this.moveToPreviousDiff()
|
||||
),
|
||||
atom.config.onDidChange('git-diff.showIconsInEditorGutter', () =>
|
||||
this.updateIconDecoration()
|
||||
),
|
||||
atom.config.onDidChange('editor.showLineNumbers', () =>
|
||||
this.updateIconDecoration()
|
||||
),
|
||||
editorElement.onDidAttach(() => this.updateIconDecoration()),
|
||||
this.editor.onDidDestroy(() => {
|
||||
this.cancelUpdate()
|
||||
@ -43,7 +50,7 @@ class GitDiffView {
|
||||
let nextDiffLineNumber = null
|
||||
let firstDiffLineNumber = null
|
||||
if (this.diffs) {
|
||||
for (const {newStart} of this.diffs) {
|
||||
for (const { newStart } of this.diffs) {
|
||||
if (newStart > cursorLineNumber) {
|
||||
if (nextDiffLineNumber == null) nextDiffLineNumber = newStart - 1
|
||||
nextDiffLineNumber = Math.min(newStart - 1, nextDiffLineNumber)
|
||||
@ -55,7 +62,10 @@ class GitDiffView {
|
||||
}
|
||||
|
||||
// Wrap around to the first diff in the file
|
||||
if (atom.config.get('git-diff.wrapAroundOnMoveToDiff') && nextDiffLineNumber == null) {
|
||||
if (
|
||||
atom.config.get('git-diff.wrapAroundOnMoveToDiff') &&
|
||||
nextDiffLineNumber == null
|
||||
) {
|
||||
nextDiffLineNumber = firstDiffLineNumber
|
||||
}
|
||||
|
||||
@ -65,7 +75,10 @@ class GitDiffView {
|
||||
updateIconDecoration () {
|
||||
const gutter = this.editor.getElement().querySelector('.gutter')
|
||||
if (gutter) {
|
||||
if (atom.config.get('editor.showLineNumbers') && atom.config.get('git-diff.showIconsInEditorGutter')) {
|
||||
if (
|
||||
atom.config.get('editor.showLineNumbers') &&
|
||||
atom.config.get('git-diff.showIconsInEditorGutter')
|
||||
) {
|
||||
gutter.classList.add('git-diff-icon')
|
||||
} else {
|
||||
gutter.classList.remove('git-diff-icon')
|
||||
@ -78,16 +91,22 @@ class GitDiffView {
|
||||
let previousDiffLineNumber = -1
|
||||
let lastDiffLineNumber = -1
|
||||
if (this.diffs) {
|
||||
for (const {newStart} of this.diffs) {
|
||||
for (const { newStart } of this.diffs) {
|
||||
if (newStart < cursorLineNumber) {
|
||||
previousDiffLineNumber = Math.max(newStart - 1, previousDiffLineNumber)
|
||||
previousDiffLineNumber = Math.max(
|
||||
newStart - 1,
|
||||
previousDiffLineNumber
|
||||
)
|
||||
}
|
||||
lastDiffLineNumber = Math.max(newStart - 1, lastDiffLineNumber)
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap around to the last diff in the file
|
||||
if (atom.config.get('git-diff.wrapAroundOnMoveToDiff') && previousDiffLineNumber === -1) {
|
||||
if (
|
||||
atom.config.get('git-diff.wrapAroundOnMoveToDiff') &&
|
||||
previousDiffLineNumber === -1
|
||||
) {
|
||||
previousDiffLineNumber = lastDiffLineNumber
|
||||
}
|
||||
|
||||
@ -104,12 +123,16 @@ class GitDiffView {
|
||||
subscribeToRepository () {
|
||||
this.repository = repositoryForPath(this.editor.getPath())
|
||||
if (this.repository) {
|
||||
this.subscriptions.add(this.repository.onDidChangeStatuses(() => {
|
||||
this.scheduleUpdate()
|
||||
}))
|
||||
this.subscriptions.add(this.repository.onDidChangeStatus(changedPath => {
|
||||
if (changedPath === this.editor.getPath()) this.scheduleUpdate()
|
||||
}))
|
||||
this.subscriptions.add(
|
||||
this.repository.onDidChangeStatuses(() => {
|
||||
this.scheduleUpdate()
|
||||
})
|
||||
)
|
||||
this.subscriptions.add(
|
||||
this.repository.onDidChangeStatus(changedPath => {
|
||||
if (changedPath === this.editor.getPath()) this.scheduleUpdate()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,16 +149,21 @@ class GitDiffView {
|
||||
if (this.editor.isDestroyed()) return
|
||||
this.removeDecorations()
|
||||
const path = this.editor && this.editor.getPath()
|
||||
if (path && this.editor.getBuffer().getLength() < MAX_BUFFER_LENGTH_TO_DIFF) {
|
||||
this.diffs = this.repository && this.repository.getLineDiffs(path, this.editor.getText())
|
||||
if (
|
||||
path &&
|
||||
this.editor.getBuffer().getLength() < MAX_BUFFER_LENGTH_TO_DIFF
|
||||
) {
|
||||
this.diffs =
|
||||
this.repository &&
|
||||
this.repository.getLineDiffs(path, this.editor.getText())
|
||||
if (this.diffs) this.addDecorations(this.diffs)
|
||||
}
|
||||
}
|
||||
|
||||
addDecorations (diffs) {
|
||||
for (const {newStart, oldLines, newLines} of diffs) {
|
||||
for (const { newStart, oldLines, newLines } of diffs) {
|
||||
const startRow = newStart - 1
|
||||
const endRow = (newStart + newLines) - 1
|
||||
const endRow = newStart + newLines - 1
|
||||
if (oldLines === 0 && newLines > 0) {
|
||||
this.markRange(startRow, endRow, 'git-line-added')
|
||||
} else if (newLines === 0 && oldLines > 0) {
|
||||
@ -156,8 +184,10 @@ class GitDiffView {
|
||||
}
|
||||
|
||||
markRange (startRow, endRow, klass) {
|
||||
const marker = this.editor.markBufferRange([[startRow, 0], [endRow, 0]], {invalidate: 'never'})
|
||||
this.editor.decorateMarker(marker, {type: 'line-number', class: klass})
|
||||
const marker = this.editor.markBufferRange([[startRow, 0], [endRow, 0]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
this.editor.decorateMarker(marker, { type: 'line-number', class: klass })
|
||||
this.markers.push(marker)
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,14 @@ module.exports = {
|
||||
if (watchedEditors.has(editor)) return
|
||||
|
||||
new GitDiffView(editor).start()
|
||||
atom.commands.add(atom.views.getView(editor), 'git-diff:toggle-diff-list', () => {
|
||||
if (diffListView == null) diffListView = new DiffListView()
|
||||
diffListView.toggle()
|
||||
})
|
||||
atom.commands.add(
|
||||
atom.views.getView(editor),
|
||||
'git-diff:toggle-diff-list',
|
||||
() => {
|
||||
if (diffListView == null) diffListView = new DiffListView()
|
||||
diffListView.toggle()
|
||||
}
|
||||
)
|
||||
|
||||
watchedEditors.add(editor)
|
||||
editor.onDidDestroy(() => watchedEditors.delete(editor))
|
||||
|
@ -8,7 +8,10 @@ describe('git-diff:toggle-diff-list', () => {
|
||||
beforeEach(() => {
|
||||
const projectPath = temp.mkdirSync('git-diff-spec-')
|
||||
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
|
||||
fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
|
||||
fs.moveSync(
|
||||
path.join(projectPath, 'git.git'),
|
||||
path.join(projectPath, '.git')
|
||||
)
|
||||
atom.project.setPaths([projectPath])
|
||||
|
||||
jasmine.attachToDOM(atom.workspace.getElement())
|
||||
@ -19,7 +22,7 @@ describe('git-diff:toggle-diff-list', () => {
|
||||
|
||||
runs(() => {
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
editor.setCursorBufferPosition([4, 29])
|
||||
editor.setCursorBufferPosition([8, 30])
|
||||
editor.insertText('a')
|
||||
atom.commands.dispatch(editor.getElement(), 'git-diff:toggle-diff-list')
|
||||
})
|
||||
@ -32,12 +35,15 @@ describe('git-diff:toggle-diff-list', () => {
|
||||
|
||||
it('shows a list of all diff hunks', () => {
|
||||
diffListView = document.querySelector('.diff-list-view ol')
|
||||
expect(diffListView.textContent).toBe('while(items.length > 0) {a-5,1 +5,1')
|
||||
expect(diffListView.textContent).toBe('while (items.length > 0) {a-9,1 +9,1')
|
||||
})
|
||||
|
||||
it('moves the cursor to the selected hunk', () => {
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
atom.commands.dispatch(document.querySelector('.diff-list-view'), 'core:confirm')
|
||||
expect(editor.getCursorBufferPosition()).toEqual([4, 4])
|
||||
atom.commands.dispatch(
|
||||
document.querySelector('.diff-list-view'),
|
||||
'core:confirm'
|
||||
)
|
||||
expect(editor.getCursorBufferPosition()).toEqual([8, 4])
|
||||
})
|
||||
})
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
90820108a054b6f49c0d21031313244b6f7d69dc
|
||||
065a272b55ec2ee84530dffd60b6869f7bf5d99c
|
||||
|
@ -1,13 +1,19 @@
|
||||
var quicksort = function () {
|
||||
var sort = function(items) {
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
return sort(left).concat(pivot).concat(sort(right));
|
||||
};
|
||||
module.exports.quicksort = function () {
|
||||
var sort = function (items) {
|
||||
if (items.length <= 1) return items
|
||||
var pivot = items.shift()
|
||||
var current
|
||||
var left = []
|
||||
var right = []
|
||||
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
while (items.length > 0) {
|
||||
current = items.shift()
|
||||
current < pivot ? left.push(current) : right.push(current)
|
||||
}
|
||||
return sort(left)
|
||||
.concat(pivot)
|
||||
.concat(sort(right))
|
||||
}
|
||||
|
||||
return sort(Array.apply(this, arguments))
|
||||
}
|
||||
|
@ -12,12 +12,17 @@ describe('GitDiff package', () => {
|
||||
const otherPath = temp.mkdirSync('some-other-path-')
|
||||
|
||||
fs.copySync(path.join(__dirname, 'fixtures', 'working-dir'), projectPath)
|
||||
fs.moveSync(path.join(projectPath, 'git.git'), path.join(projectPath, '.git'))
|
||||
fs.moveSync(
|
||||
path.join(projectPath, 'git.git'),
|
||||
path.join(projectPath, '.git')
|
||||
)
|
||||
atom.project.setPaths([otherPath, projectPath])
|
||||
|
||||
jasmine.attachToDOM(atom.workspace.getElement())
|
||||
|
||||
waitsForPromise(() => atom.workspace.open(path.join(projectPath, 'sample.js')))
|
||||
waitsForPromise(() =>
|
||||
atom.workspace.open(path.join(projectPath, 'sample.js'))
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
@ -29,11 +34,18 @@ describe('GitDiff package', () => {
|
||||
|
||||
describe('when the editor has modified lines', () => {
|
||||
it('highlights the modified lines', () => {
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(0)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
|
||||
0
|
||||
)
|
||||
editor.insertText('a')
|
||||
advanceClock(editor.getBuffer().stoppedChangingDelay)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(1)
|
||||
expect(editorElement.querySelector('.git-line-modified')).toHaveData('buffer-row', 0)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
|
||||
1
|
||||
)
|
||||
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
|
||||
'buffer-row',
|
||||
0
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -45,7 +57,10 @@ describe('GitDiff package', () => {
|
||||
editor.insertText('a')
|
||||
advanceClock(editor.getBuffer().stoppedChangingDelay)
|
||||
expect(editorElement.querySelectorAll('.git-line-added').length).toBe(1)
|
||||
expect(editorElement.querySelector('.git-line-added')).toHaveData('buffer-row', 1)
|
||||
expect(editorElement.querySelector('.git-line-added')).toHaveData(
|
||||
'buffer-row',
|
||||
1
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -56,7 +71,10 @@ describe('GitDiff package', () => {
|
||||
editor.deleteLine()
|
||||
advanceClock(editor.getBuffer().stoppedChangingDelay)
|
||||
expect(editorElement.querySelectorAll('.git-line-removed').length).toBe(1)
|
||||
expect(editorElement.querySelector('.git-line-removed')).toHaveData('buffer-row', 4)
|
||||
expect(editorElement.querySelector('.git-line-removed')).toHaveData(
|
||||
'buffer-row',
|
||||
4
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -66,29 +84,44 @@ describe('GitDiff package', () => {
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.deleteLine()
|
||||
advanceClock(editor.getBuffer().stoppedChangingDelay)
|
||||
expect(editorElement.querySelectorAll('.git-previous-line-removed').length).toBe(1)
|
||||
expect(editorElement.querySelector('.git-previous-line-removed')).toHaveData('buffer-row', 0)
|
||||
expect(
|
||||
editorElement.querySelectorAll('.git-previous-line-removed').length
|
||||
).toBe(1)
|
||||
expect(
|
||||
editorElement.querySelector('.git-previous-line-removed')
|
||||
).toHaveData('buffer-row', 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a modified line is restored to the HEAD version contents', () => {
|
||||
it('removes the diff highlight', () => {
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(0)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
|
||||
0
|
||||
)
|
||||
editor.insertText('a')
|
||||
advanceClock(editor.getBuffer().stoppedChangingDelay)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(1)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
|
||||
1
|
||||
)
|
||||
editor.backspace()
|
||||
advanceClock(editor.getBuffer().stoppedChangingDelay)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(0)
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(
|
||||
0
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a modified file is opened', () => {
|
||||
it('highlights the changed lines', () => {
|
||||
fs.writeFileSync(path.join(projectPath, 'sample.txt'), 'Some different text.')
|
||||
fs.writeFileSync(
|
||||
path.join(projectPath, 'sample.txt'),
|
||||
'Some different text.'
|
||||
)
|
||||
let nextTick = false
|
||||
|
||||
waitsForPromise(() => atom.workspace.open(path.join(projectPath, 'sample.txt')))
|
||||
waitsForPromise(() =>
|
||||
atom.workspace.open(path.join(projectPath, 'sample.txt'))
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
editorElement = atom.workspace.getActiveTextEditor().getElement()
|
||||
@ -101,8 +134,13 @@ describe('GitDiff package', () => {
|
||||
waitsFor(() => nextTick)
|
||||
|
||||
runs(() => {
|
||||
expect(editorElement.querySelectorAll('.git-line-modified').length).toBe(1)
|
||||
expect(editorElement.querySelector('.git-line-modified')).toHaveData('buffer-row', 0)
|
||||
expect(
|
||||
editorElement.querySelectorAll('.git-line-modified').length
|
||||
).toBe(1)
|
||||
expect(editorElement.querySelector('.git-line-modified')).toHaveData(
|
||||
'buffer-row',
|
||||
0
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -148,7 +186,9 @@ describe('GitDiff package', () => {
|
||||
})
|
||||
|
||||
describe('when the wrapAroundOnMoveToDiff config option is false', () => {
|
||||
beforeEach(() => atom.config.set('git-diff.wrapAroundOnMoveToDiff', false))
|
||||
beforeEach(() =>
|
||||
atom.config.set('git-diff.wrapAroundOnMoveToDiff', false)
|
||||
)
|
||||
|
||||
it('does not wraps around to the first/last diff in the file', () => {
|
||||
editor.insertText('a')
|
||||
@ -177,19 +217,28 @@ describe('GitDiff package', () => {
|
||||
atom.config.set('git-diff.showIconsInEditorGutter', true)
|
||||
})
|
||||
|
||||
it('the gutter has a git-diff-icon class', () => expect(editorElement.querySelector('.gutter')).toHaveClass('git-diff-icon'))
|
||||
it('the gutter has a git-diff-icon class', () =>
|
||||
expect(editorElement.querySelector('.gutter')).toHaveClass(
|
||||
'git-diff-icon'
|
||||
))
|
||||
|
||||
it('keeps the git-diff-icon class when editor.showLineNumbers is toggled', () => {
|
||||
atom.config.set('editor.showLineNumbers', false)
|
||||
expect(editorElement.querySelector('.gutter')).not.toHaveClass('git-diff-icon')
|
||||
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
|
||||
'git-diff-icon'
|
||||
)
|
||||
|
||||
atom.config.set('editor.showLineNumbers', true)
|
||||
expect(editorElement.querySelector('.gutter')).toHaveClass('git-diff-icon')
|
||||
expect(editorElement.querySelector('.gutter')).toHaveClass(
|
||||
'git-diff-icon'
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the git-diff-icon class when the showIconsInEditorGutter config option set to false', () => {
|
||||
atom.config.set('git-diff.showIconsInEditorGutter', false)
|
||||
expect(editorElement.querySelector('.gutter')).not.toHaveClass('git-diff-icon')
|
||||
expect(editorElement.querySelector('.gutter')).not.toHaveClass(
|
||||
'git-diff-icon'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -21,7 +21,7 @@ atom-text-editor {
|
||||
left: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
content: " ";
|
||||
content: "";
|
||||
border: solid transparent;
|
||||
border-left-color: @syntax-color-removed;
|
||||
border-width: @size;
|
||||
@ -37,22 +37,19 @@ atom-text-editor {
|
||||
}
|
||||
|
||||
.gutter.git-diff-icon .line-number {
|
||||
width: 100%;
|
||||
border-left: none;
|
||||
padding-left: 0.4em;
|
||||
border-left-width: 0;
|
||||
padding-left: 1.4em; // space for diff icon
|
||||
|
||||
&:before {
|
||||
.octicon-font();
|
||||
.octicon-font();
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -.05em;
|
||||
|
||||
// make sure it doesnt affect the gutter line height.
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
top: .2em;
|
||||
left: .4em;
|
||||
height: 0px; // make sure it doesnt affect the gutter line height.
|
||||
width: 1em;
|
||||
content: " ";
|
||||
padding-right: 0.4em;
|
||||
font-size: .95em;
|
||||
font-size: .75em;
|
||||
}
|
||||
|
||||
&.git-line-modified:before {
|
||||
@ -70,13 +67,12 @@ atom-text-editor {
|
||||
border: none; // reset triangle
|
||||
content: @dash;
|
||||
color: @syntax-color-removed;
|
||||
position: relative;
|
||||
}
|
||||
&.git-line-removed:before {
|
||||
top: .6em;
|
||||
top: 1em;
|
||||
}
|
||||
&.git-previous-line-removed:before {
|
||||
top: -.6em;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,13 +29,13 @@ class GoToLineView {
|
||||
atom.commands.add(this.miniEditor.element, 'core:cancel', () => {
|
||||
this.close()
|
||||
})
|
||||
this.miniEditor.onWillInsertText((arg) => {
|
||||
this.miniEditor.onWillInsertText(arg => {
|
||||
if (arg.text.match(/[^0-9:]/)) {
|
||||
arg.cancel()
|
||||
}
|
||||
})
|
||||
this.miniEditor.onDidChange(() => {
|
||||
this.navigate({keepOpen: true})
|
||||
this.navigate({ keepOpen: true })
|
||||
})
|
||||
}
|
||||
|
||||
@ -62,9 +62,11 @@ class GoToLineView {
|
||||
|
||||
const currentRow = editor.getCursorBufferPosition().row
|
||||
const rowLineNumber = lineNumber.split(/:+/)[0] || ''
|
||||
const row = rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow
|
||||
const row =
|
||||
rowLineNumber.length > 0 ? parseInt(rowLineNumber) - 1 : currentRow
|
||||
const columnLineNumber = lineNumber.split(/:+/)[1] || ''
|
||||
const column = columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1
|
||||
const column =
|
||||
columnLineNumber.length > 0 ? parseInt(columnLineNumber) - 1 : -1
|
||||
|
||||
const position = new Point(row, column)
|
||||
editor.setCursorBufferPosition(position)
|
||||
@ -83,7 +85,10 @@ class GoToLineView {
|
||||
}
|
||||
|
||||
restoreFocus () {
|
||||
if (this.previouslyFocusedElement && this.previouslyFocusedElement.parentElement) {
|
||||
if (
|
||||
this.previouslyFocusedElement &&
|
||||
this.previouslyFocusedElement.parentElement
|
||||
) {
|
||||
return this.previouslyFocusedElement.focus()
|
||||
}
|
||||
atom.views.getView(atom.workspace).focus()
|
||||
@ -93,7 +98,8 @@ class GoToLineView {
|
||||
if (this.panel.isVisible() || !atom.workspace.getActiveTextEditor()) return
|
||||
this.storeFocusedElement()
|
||||
this.panel.show()
|
||||
this.message.textContent = 'Enter a <row> or <row>:<column> to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'
|
||||
this.message.textContent =
|
||||
'Enter a <row> or <row>:<column> to go there. Examples: "3" for row 3 or "2:7" for row 2 and column 7'
|
||||
this.miniEditor.element.focus()
|
||||
}
|
||||
}
|
||||
|
108
packages/go-to-line/spec/fixtures/sample.js
vendored
108
packages/go-to-line/spec/fixtures/sample.js
vendored
@ -1,70 +1,84 @@
|
||||
var quicksort = function () {
|
||||
var sort = function(items) {
|
||||
if (items.length <= 1) return items;
|
||||
var pivot = items.shift(), current, left = [], right = [];
|
||||
while(items.length > 0) {
|
||||
current = items.shift();
|
||||
current < pivot ? left.push(current) : right.push(current);
|
||||
}
|
||||
return sort(left).concat(pivot).concat(sort(right));
|
||||
};
|
||||
var sort = function (items) {
|
||||
if (items.length <= 1) return items
|
||||
var pivot = items.shift()
|
||||
var current
|
||||
var left = []
|
||||
var right = []
|
||||
|
||||
return sort(Array.apply(this, arguments));
|
||||
};
|
||||
while (items.length > 0) {
|
||||
current = items.shift()
|
||||
current < pivot ? left.push(current) : right.push(current)
|
||||
}
|
||||
return sort(left)
|
||||
.concat(pivot)
|
||||
.concat(sort(right))
|
||||
}
|
||||
|
||||
return sort(Array.apply(this, arguments))
|
||||
}
|
||||
|
||||
// adapted from:
|
||||
// https://github.com/nzakas/computer-science-in-javascript/tree/master/algorithms/sorting/merge-sort-recursive
|
||||
var mergeSort function (items){
|
||||
var merge = function (left, right){
|
||||
var result = [];
|
||||
var il = 0;
|
||||
var ir = 0;
|
||||
var mergeSort = function (items) {
|
||||
var merge = function (left, right) {
|
||||
var result = []
|
||||
var il = 0
|
||||
var ir = 0
|
||||
|
||||
while (il < left.length && ir < right.length){
|
||||
if (left[il] < right[ir]){
|
||||
result.push(left[il++]);
|
||||
while (il < left.length && ir < right.length) {
|
||||
if (left[il] < right[ir]) {
|
||||
result.push(left[il++])
|
||||
} else {
|
||||
result.push(right[ir++]);
|
||||
result.push(right[ir++])
|
||||
}
|
||||
}
|
||||
|
||||
return result.concat(left.slice(il)).concat(right.slice(ir));
|
||||
};
|
||||
|
||||
if (items.length < 2) {
|
||||
return items;
|
||||
return result.concat(left.slice(il)).concat(right.slice(ir))
|
||||
}
|
||||
|
||||
var middle = Math.floor(items.length / 2),
|
||||
left = items.slice(0, middle),
|
||||
right = items.slice(middle),
|
||||
params = merge(mergeSort(left), mergeSort(right));
|
||||
if (items.length < 2) {
|
||||
return items
|
||||
}
|
||||
|
||||
var middle = Math.floor(items.length / 2)
|
||||
var left = items.slice(0, middle)
|
||||
var right = items.slice(middle)
|
||||
var params = merge(mergeSort(left), mergeSort(right))
|
||||
|
||||
// Add the arguments to replace everything between 0 and last item in the array
|
||||
params.unshift(0, items.length);
|
||||
items.splice.apply(items, params);
|
||||
return items;
|
||||
};
|
||||
params.unshift(0, items.length)
|
||||
items.splice.apply(items, params)
|
||||
return items
|
||||
}
|
||||
|
||||
// adapted from:
|
||||
// https://github.com/nzakas/computer-science-in-javascript/blob/master/algorithms/sorting/bubble-sort/bubble-sort.js
|
||||
var bubbleSort = function (items){
|
||||
var swap = function (items, firstIndex, secondIndex){
|
||||
var temp = items[firstIndex];
|
||||
items[firstIndex] = items[secondIndex];
|
||||
items[secondIndex] = temp;
|
||||
};
|
||||
var bubbleSort = function (items) {
|
||||
var swap = function (items, firstIndex, secondIndex) {
|
||||
var temp = items[firstIndex]
|
||||
items[firstIndex] = items[secondIndex]
|
||||
items[secondIndex] = temp
|
||||
}
|
||||
|
||||
var len = items.length,
|
||||
i, j, stop;
|
||||
var len = items.length
|
||||
var i
|
||||
var j
|
||||
var stop
|
||||
|
||||
for (i=0; i < len; i++){
|
||||
for (j=0, stop=len-i; j < stop; j++){
|
||||
if (items[j] > items[j+1]){
|
||||
swap(items, j, j+1);
|
||||
for (i = 0; i < len; i++) {
|
||||
for (j = 0, stop = len - i; j < stop; j++) {
|
||||
if (items[j] > items[j + 1]) {
|
||||
swap(items, j, j + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
return items
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bubbleSort,
|
||||
mergeSort,
|
||||
quicksort
|
||||
}
|
||||
|
@ -51,8 +51,8 @@ describe('GoToLine', () => {
|
||||
|
||||
describe('when typing line numbers (auto-navigation)', () => {
|
||||
it('automatically scrolls to the desired line', () => {
|
||||
goToLine.miniEditor.insertText('13')
|
||||
expect(editor.getCursorBufferPosition()).toEqual([12, 0])
|
||||
goToLine.miniEditor.insertText('19')
|
||||
expect(editor.getCursorBufferPosition()).toEqual([18, 0])
|
||||
})
|
||||
})
|
||||
|
||||
@ -76,8 +76,12 @@ describe('GoToLine', () => {
|
||||
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
|
||||
const rowsPerPage = editor.getRowsPerPage()
|
||||
const currentRow = editor.getCursorBufferPosition().row - 1
|
||||
expect(editor.getFirstVisibleScreenRow()).toBe(currentRow - Math.ceil(rowsPerPage / 2))
|
||||
expect(editor.getLastVisibleScreenRow()).toBe(currentRow + Math.floor(rowsPerPage / 2))
|
||||
expect(editor.getFirstVisibleScreenRow()).toBe(
|
||||
currentRow - Math.ceil(rowsPerPage / 2)
|
||||
)
|
||||
expect(editor.getLastVisibleScreenRow()).toBe(
|
||||
currentRow + Math.floor(rowsPerPage / 2)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -86,10 +90,10 @@ describe('GoToLine', () => {
|
||||
atom.commands.dispatch(editorView, 'go-to-line:toggle')
|
||||
expect(goToLine.panel.isVisible()).toBeTruthy()
|
||||
expect(goToLine.miniEditor.getText()).toBe('')
|
||||
goToLine.miniEditor.insertText('71')
|
||||
goToLine.miniEditor.insertText('78')
|
||||
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
|
||||
expect(goToLine.panel.isVisible()).toBeFalsy()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([70, 0])
|
||||
expect(editor.getCursorBufferPosition()).toEqual([77, 0])
|
||||
})
|
||||
})
|
||||
|
||||
@ -101,7 +105,7 @@ describe('GoToLine', () => {
|
||||
goToLine.miniEditor.insertText('3:43')
|
||||
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
|
||||
expect(goToLine.panel.isVisible()).toBeFalsy()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([2, 40])
|
||||
expect(editor.getCursorBufferPosition()).toEqual([2, 39])
|
||||
})
|
||||
})
|
||||
|
||||
@ -116,12 +120,12 @@ describe('GoToLine', () => {
|
||||
|
||||
describe('when the line number entered is nested within foldes', () => {
|
||||
it('unfolds all folds containing the given row', () => {
|
||||
expect(editor.indentationForBufferRow(6)).toEqual(3)
|
||||
expect(editor.indentationForBufferRow(9)).toEqual(3)
|
||||
editor.foldAll()
|
||||
expect(editor.screenRowForBufferRow(6)).toEqual(0)
|
||||
goToLine.miniEditor.insertText('7')
|
||||
expect(editor.screenRowForBufferRow(9)).toEqual(0)
|
||||
goToLine.miniEditor.insertText('10')
|
||||
atom.commands.dispatch(goToLine.miniEditor.element, 'core:confirm')
|
||||
expect(editor.getCursorBufferPosition()).toEqual([6, 6])
|
||||
expect(editor.getCursorBufferPosition()).toEqual([9, 6])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,13 @@
|
||||
const SelectListView = require('atom-select-list')
|
||||
|
||||
module.exports =
|
||||
class GrammarListView {
|
||||
module.exports = class GrammarListView {
|
||||
constructor () {
|
||||
this.autoDetect = {name: 'Auto Detect'}
|
||||
this.autoDetect = { name: 'Auto Detect' }
|
||||
this.selectListView = new SelectListView({
|
||||
itemsClassList: ['mark-active'],
|
||||
items: [],
|
||||
filterKeyForItem: (grammar) => grammar.name,
|
||||
elementForItem: (grammar) => {
|
||||
filterKeyForItem: grammar => grammar.name,
|
||||
elementForItem: grammar => {
|
||||
const grammarName = grammar.name || grammar.scopeName
|
||||
const element = document.createElement('li')
|
||||
if (grammar === this.currentGrammar) {
|
||||
@ -38,7 +37,7 @@ class GrammarListView {
|
||||
|
||||
return element
|
||||
},
|
||||
didConfirmSelection: (grammar) => {
|
||||
didConfirmSelection: grammar => {
|
||||
this.cancel()
|
||||
if (grammar === this.autoDetect) {
|
||||
atom.textEditors.clearGrammarOverride(this.editor)
|
||||
@ -73,7 +72,7 @@ class GrammarListView {
|
||||
attach () {
|
||||
this.previouslyFocusedElement = document.activeElement
|
||||
if (this.panel == null) {
|
||||
this.panel = atom.workspace.addModalPanel({item: this.selectListView})
|
||||
this.panel = atom.workspace.addModalPanel({ item: this.selectListView })
|
||||
}
|
||||
this.selectListView.focus()
|
||||
this.selectListView.reset()
|
||||
@ -125,7 +124,7 @@ class GrammarListView {
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
grammars.unshift(this.autoDetect)
|
||||
await this.selectListView.update({items: grammars})
|
||||
await this.selectListView.update({ items: grammars })
|
||||
this.attach()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
const {Disposable} = require('atom')
|
||||
const { Disposable } = require('atom')
|
||||
|
||||
module.exports =
|
||||
class GrammarStatusView {
|
||||
module.exports = class GrammarStatusView {
|
||||
constructor (statusBar) {
|
||||
this.statusBar = statusBar
|
||||
this.element = document.createElement('grammar-selector-status')
|
||||
@ -10,15 +9,25 @@ class GrammarStatusView {
|
||||
this.grammarLink.classList.add('inline-block')
|
||||
this.element.appendChild(this.grammarLink)
|
||||
|
||||
this.activeItemSubscription = atom.workspace.observeActiveTextEditor(this.subscribeToActiveTextEditor.bind(this))
|
||||
this.activeItemSubscription = atom.workspace.observeActiveTextEditor(
|
||||
this.subscribeToActiveTextEditor.bind(this)
|
||||
)
|
||||
|
||||
this.configSubscription = atom.config.observe('grammar-selector.showOnRightSideOfStatusBar', this.attach.bind(this))
|
||||
const clickHandler = (event) => {
|
||||
this.configSubscription = atom.config.observe(
|
||||
'grammar-selector.showOnRightSideOfStatusBar',
|
||||
this.attach.bind(this)
|
||||
)
|
||||
const clickHandler = event => {
|
||||
event.preventDefault()
|
||||
atom.commands.dispatch(atom.views.getView(atom.workspace.getActiveTextEditor()), 'grammar-selector:show')
|
||||
atom.commands.dispatch(
|
||||
atom.views.getView(atom.workspace.getActiveTextEditor()),
|
||||
'grammar-selector:show'
|
||||
)
|
||||
}
|
||||
this.element.addEventListener('click', clickHandler)
|
||||
this.clickSubscription = new Disposable(() => { this.element.removeEventListener('click', clickHandler) })
|
||||
this.clickSubscription = new Disposable(() => {
|
||||
this.element.removeEventListener('click', clickHandler)
|
||||
})
|
||||
}
|
||||
|
||||
attach () {
|
||||
@ -27,8 +36,8 @@ class GrammarStatusView {
|
||||
}
|
||||
|
||||
this.tile = atom.config.get('grammar-selector.showOnRightSideOfStatusBar')
|
||||
? this.statusBar.addRightTile({item: this.element, priority: 10})
|
||||
: this.statusBar.addLeftTile({item: this.element, priority: 10})
|
||||
? this.statusBar.addRightTile({ item: this.element, priority: 10 })
|
||||
: this.statusBar.addLeftTile({ item: this.element, priority: 10 })
|
||||
}
|
||||
|
||||
destroy () {
|
||||
@ -65,7 +74,9 @@ class GrammarStatusView {
|
||||
|
||||
const editor = atom.workspace.getActiveTextEditor()
|
||||
if (editor) {
|
||||
this.grammarSubscription = editor.onDidChangeGrammar(this.updateGrammarText.bind(this))
|
||||
this.grammarSubscription = editor.onDidChangeGrammar(
|
||||
this.updateGrammarText.bind(this)
|
||||
)
|
||||
}
|
||||
this.updateGrammarText()
|
||||
}
|
||||
@ -92,7 +103,9 @@ class GrammarStatusView {
|
||||
this.grammarLink.dataset.grammar = grammarName
|
||||
this.element.style.display = ''
|
||||
|
||||
this.tooltip = atom.tooltips.add(this.element, {title: `File uses the ${grammarName} grammar`})
|
||||
this.tooltip = atom.tooltips.add(this.element, {
|
||||
title: `File uses the ${grammarName} grammar`
|
||||
})
|
||||
} else {
|
||||
this.element.style.display = 'none'
|
||||
}
|
||||
|
@ -7,10 +7,14 @@ let grammarStatusView = null
|
||||
|
||||
module.exports = {
|
||||
activate () {
|
||||
commandDisposable = atom.commands.add('atom-text-editor', 'grammar-selector:show', () => {
|
||||
if (!grammarListView) grammarListView = new GrammarListView()
|
||||
grammarListView.toggle()
|
||||
})
|
||||
commandDisposable = atom.commands.add(
|
||||
'atom-text-editor',
|
||||
'grammar-selector:show',
|
||||
() => {
|
||||
if (!grammarListView) grammarListView = new GrammarListView()
|
||||
grammarListView.toggle()
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
|
@ -1,42 +0,0 @@
|
||||
exports.beforeEach = function beforeEach (fn) {
|
||||
global.beforeEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise('beforeEach promise', result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.afterEach = function afterEach (fn) {
|
||||
global.afterEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise('afterEach promise', result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
;['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
exports[name] = function (description, fn) {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise('test promise', result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function waitsForPromise (message, promise) {
|
||||
global.waitsFor(message, (done) => {
|
||||
promise.then(done, (error) => {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
const path = require('path')
|
||||
const SelectListView = require('atom-select-list')
|
||||
const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line
|
||||
|
||||
describe('GrammarSelector', () => {
|
||||
let [editor, textGrammar, jsGrammar] = []
|
||||
@ -14,7 +13,9 @@ describe('GrammarSelector', () => {
|
||||
await atom.packages.activatePackage('grammar-selector')
|
||||
await atom.packages.activatePackage('language-text')
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-with-no-name'))
|
||||
await atom.packages.activatePackage(
|
||||
path.join(__dirname, 'fixtures', 'language-with-no-name')
|
||||
)
|
||||
|
||||
editor = await atom.workspace.open('sample.js')
|
||||
|
||||
@ -44,8 +45,7 @@ describe('GrammarSelector', () => {
|
||||
const grammarView = await getGrammarView(editor)
|
||||
grammarView.props.didConfirmSelection(textGrammar)
|
||||
expect(editor.getGrammar()).toBe(textGrammar)
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe('when auto-detect is selected', () =>
|
||||
it('restores the auto-detected grammar on the editor', async () => {
|
||||
@ -56,8 +56,7 @@ describe('GrammarSelector', () => {
|
||||
grammarView = await getGrammarView(editor)
|
||||
grammarView.props.didConfirmSelection(grammarView.items[0])
|
||||
expect(editor.getGrammar()).toBe(jsGrammar)
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe("when the editor's current grammar is the null grammar", () =>
|
||||
it('displays Auto Detect as the selected grammar', async () => {
|
||||
@ -75,8 +74,7 @@ describe('GrammarSelector', () => {
|
||||
const grammarView = await getGrammarView(editor)
|
||||
grammarView.props.didConfirmSelection(jsGrammar)
|
||||
expect(editor.getGrammar()).toBe(jsGrammar)
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe('Status bar grammar label', () => {
|
||||
let [grammarStatus, grammarTile, statusBar] = []
|
||||
@ -95,7 +93,9 @@ describe('GrammarSelector', () => {
|
||||
|
||||
it('displays the name of the current grammar', () => {
|
||||
expect(grammarStatus.querySelector('a').textContent).toBe('JavaScript')
|
||||
expect(getTooltipText(grammarStatus)).toBe('File uses the JavaScript grammar')
|
||||
expect(getTooltipText(grammarStatus)).toBe(
|
||||
'File uses the JavaScript grammar'
|
||||
)
|
||||
})
|
||||
|
||||
it('displays Plain Text when the current grammar is the null grammar', async () => {
|
||||
@ -104,7 +104,9 @@ describe('GrammarSelector', () => {
|
||||
|
||||
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text')
|
||||
expect(grammarStatus).toBeVisible()
|
||||
expect(getTooltipText(grammarStatus)).toBe('File uses the Plain Text grammar')
|
||||
expect(getTooltipText(grammarStatus)).toBe(
|
||||
'File uses the Plain Text grammar'
|
||||
)
|
||||
|
||||
editor.setGrammar(atom.grammars.grammarForScopeName('source.js'))
|
||||
await atom.views.getNextUpdatePromise()
|
||||
@ -123,20 +125,31 @@ describe('GrammarSelector', () => {
|
||||
|
||||
describe('when the grammar-selector.showOnRightSideOfStatusBar setting changes', () =>
|
||||
it('moves the item to the preferred side of the status bar', () => {
|
||||
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(grammarStatus)
|
||||
expect(statusBar.getRightTiles().map(tile => tile.getItem())).not.toContain(grammarStatus)
|
||||
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(
|
||||
grammarStatus
|
||||
)
|
||||
expect(
|
||||
statusBar.getRightTiles().map(tile => tile.getItem())
|
||||
).not.toContain(grammarStatus)
|
||||
|
||||
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', true)
|
||||
|
||||
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).not.toContain(grammarStatus)
|
||||
expect(statusBar.getRightTiles().map(tile => tile.getItem())).toContain(grammarStatus)
|
||||
expect(
|
||||
statusBar.getLeftTiles().map(tile => tile.getItem())
|
||||
).not.toContain(grammarStatus)
|
||||
expect(statusBar.getRightTiles().map(tile => tile.getItem())).toContain(
|
||||
grammarStatus
|
||||
)
|
||||
|
||||
atom.config.set('grammar-selector.showOnRightSideOfStatusBar', false)
|
||||
|
||||
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(grammarStatus)
|
||||
expect(statusBar.getRightTiles().map(tile => tile.getItem())).not.toContain(grammarStatus)
|
||||
})
|
||||
)
|
||||
expect(statusBar.getLeftTiles().map(tile => tile.getItem())).toContain(
|
||||
grammarStatus
|
||||
)
|
||||
expect(
|
||||
statusBar.getRightTiles().map(tile => tile.getItem())
|
||||
).not.toContain(grammarStatus)
|
||||
}))
|
||||
|
||||
describe("when the editor's grammar changes", () =>
|
||||
it('displays the new grammar of the editor', async () => {
|
||||
@ -144,15 +157,18 @@ describe('GrammarSelector', () => {
|
||||
await atom.views.getNextUpdatePromise()
|
||||
|
||||
expect(grammarStatus.querySelector('a').textContent).toBe('Plain Text')
|
||||
expect(getTooltipText(grammarStatus)).toBe('File uses the Plain Text grammar')
|
||||
expect(getTooltipText(grammarStatus)).toBe(
|
||||
'File uses the Plain Text grammar'
|
||||
)
|
||||
|
||||
editor.setGrammar(atom.grammars.grammarForScopeName('source.a'))
|
||||
await atom.views.getNextUpdatePromise()
|
||||
|
||||
expect(grammarStatus.querySelector('a').textContent).toBe('source.a')
|
||||
expect(getTooltipText(grammarStatus)).toBe('File uses the source.a grammar')
|
||||
})
|
||||
)
|
||||
expect(getTooltipText(grammarStatus)).toBe(
|
||||
'File uses the source.a grammar'
|
||||
)
|
||||
}))
|
||||
|
||||
describe('when toggling hideDuplicateTextMateGrammars', () => {
|
||||
it('shows only the Tree-sitter if true and both exist', async () => {
|
||||
@ -203,19 +219,21 @@ describe('GrammarSelector', () => {
|
||||
describe('when clicked', () =>
|
||||
it('shows the grammar selector modal', () => {
|
||||
const eventHandler = jasmine.createSpy('eventHandler')
|
||||
atom.commands.add(editor.getElement(), 'grammar-selector:show', eventHandler)
|
||||
atom.commands.add(
|
||||
editor.getElement(),
|
||||
'grammar-selector:show',
|
||||
eventHandler
|
||||
)
|
||||
grammarStatus.click()
|
||||
expect(eventHandler).toHaveBeenCalled()
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe('when the package is deactivated', () =>
|
||||
it('removes the view', () => {
|
||||
spyOn(grammarTile, 'destroy')
|
||||
atom.packages.deactivatePackage('grammar-selector')
|
||||
expect(grammarTile.destroy).toHaveBeenCalled()
|
||||
})
|
||||
)
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
/** @babel */
|
||||
/** @jsx etch.dom */
|
||||
|
||||
import {BufferedProcess} from 'atom'
|
||||
import etch from 'etch'
|
||||
|
||||
import VIEW_URI from './view-uri'
|
||||
@ -11,8 +10,8 @@ const REBUILD_SUCCEEDED = 'rebuild-succeeded'
|
||||
|
||||
export default class IncompatiblePackagesComponent {
|
||||
constructor (packageManager) {
|
||||
this.rebuildStatuses = new Map
|
||||
this.rebuildFailureOutputs = new Map
|
||||
this.rebuildStatuses = new Map()
|
||||
this.rebuildFailureOutputs = new Map()
|
||||
this.rebuildInProgress = false
|
||||
this.rebuiltPackageCount = 0
|
||||
this.packageManager = packageManager
|
||||
@ -25,13 +24,15 @@ export default class IncompatiblePackagesComponent {
|
||||
global.setImmediate(this.populateIncompatiblePackages.bind(this))
|
||||
}
|
||||
|
||||
this.element.addEventListener('click', (event) => {
|
||||
this.element.addEventListener('click', event => {
|
||||
if (event.target === this.refs.rebuildButton) {
|
||||
this.rebuildIncompatiblePackages()
|
||||
} else if (event.target === this.refs.reloadButton) {
|
||||
atom.reload()
|
||||
} else if (event.target.classList.contains('view-settings')) {
|
||||
atom.workspace.open(`atom://config/packages/${event.target.package.name}`)
|
||||
atom.workspace.open(
|
||||
`atom://config/packages/${event.target.package.name}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -44,7 +45,10 @@ export default class IncompatiblePackagesComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='incompatible-packages padded native-key-bindings' tabIndex='-1'>
|
||||
<div
|
||||
className='incompatible-packages padded native-key-bindings'
|
||||
tabIndex='-1'
|
||||
>
|
||||
{this.renderHeading()}
|
||||
{this.renderIncompatiblePackageList()}
|
||||
</div>
|
||||
@ -55,15 +59,14 @@ export default class IncompatiblePackagesComponent {
|
||||
if (this.incompatiblePackages.length > 0) {
|
||||
if (this.rebuiltPackageCount > 0) {
|
||||
let alertClass =
|
||||
(this.rebuiltPackageCount === this.incompatiblePackages.length)
|
||||
this.rebuiltPackageCount === this.incompatiblePackages.length
|
||||
? 'alert-success icon-check'
|
||||
: 'alert-warning icon-bug'
|
||||
|
||||
return (
|
||||
<div className={'alert icon ' + alertClass}>
|
||||
{this.rebuiltPackageCount} of {this.incompatiblePackages.length} packages
|
||||
were rebuilt successfully. Reload Atom to activate them.
|
||||
|
||||
{this.rebuiltPackageCount} of {this.incompatiblePackages.length}{' '}
|
||||
packages were rebuilt successfully. Reload Atom to activate them.
|
||||
<button ref='reloadButton' className='btn pull-right'>
|
||||
Reload Atom
|
||||
</button>
|
||||
@ -72,10 +75,13 @@ export default class IncompatiblePackagesComponent {
|
||||
} else {
|
||||
return (
|
||||
<div className='alert alert-danger icon icon-bug'>
|
||||
Some installed packages could not be loaded because they contain native
|
||||
modules that were compiled for an earlier version of Atom.
|
||||
|
||||
<button ref='rebuildButton' className='btn pull-right' disabled={this.rebuildInProgress}>
|
||||
Some installed packages could not be loaded because they contain
|
||||
native modules that were compiled for an earlier version of Atom.
|
||||
<button
|
||||
ref='rebuildButton'
|
||||
className='btn pull-right'
|
||||
disabled={this.rebuildInProgress}
|
||||
>
|
||||
Rebuild Packages
|
||||
</button>
|
||||
</div>
|
||||
@ -92,9 +98,11 @@ export default class IncompatiblePackagesComponent {
|
||||
|
||||
renderIncompatiblePackageList () {
|
||||
return (
|
||||
<div>{
|
||||
this.incompatiblePackages.map(this.renderIncompatiblePackage.bind(this))
|
||||
}</div>
|
||||
<div>
|
||||
{this.incompatiblePackages.map(
|
||||
this.renderIncompatiblePackage.bind(this)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -104,15 +112,18 @@ export default class IncompatiblePackagesComponent {
|
||||
return (
|
||||
<div className={'incompatible-package'}>
|
||||
{this.renderRebuildStatusIndicator(rebuildStatus)}
|
||||
<button className='btn view-settings icon icon-gear pull-right' package={pack}>Package Settings</button>
|
||||
<button
|
||||
className='btn view-settings icon icon-gear pull-right'
|
||||
package={pack}
|
||||
>
|
||||
Package Settings
|
||||
</button>
|
||||
<h4 className='heading'>
|
||||
{pack.name} {pack.metadata.version}
|
||||
</h4>
|
||||
{
|
||||
rebuildStatus
|
||||
{rebuildStatus
|
||||
? this.renderRebuildOutput(pack)
|
||||
: this.renderIncompatibleModules(pack)
|
||||
}
|
||||
: this.renderIncompatibleModules(pack)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -151,23 +162,23 @@ export default class IncompatiblePackagesComponent {
|
||||
|
||||
renderIncompatibleModules (pack) {
|
||||
return (
|
||||
<ul>{
|
||||
pack.incompatibleModules.map((nativeModule) =>
|
||||
<ul>
|
||||
{pack.incompatibleModules.map(nativeModule => (
|
||||
<li>
|
||||
<div className='icon icon-file-binary'>
|
||||
{nativeModule.name}@{nativeModule.version || 'unknown'} – <span className='text-warning'>{nativeModule.error}</span>
|
||||
{nativeModule.name}@{nativeModule.version || 'unknown'} –{' '}
|
||||
<span className='text-warning'>{nativeModule.error}</span>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}</ul>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
populateIncompatiblePackages () {
|
||||
this.incompatiblePackages =
|
||||
this.packageManager
|
||||
.getLoadedPackages()
|
||||
.filter(pack => !pack.isCompatible())
|
||||
this.incompatiblePackages = this.packageManager
|
||||
.getLoadedPackages()
|
||||
.filter(pack => !pack.isCompatible())
|
||||
|
||||
for (let pack of this.incompatiblePackages) {
|
||||
let buildFailureOutput = pack.getBuildFailureOutput()
|
||||
@ -186,7 +197,7 @@ export default class IncompatiblePackagesComponent {
|
||||
let rebuiltPackageCount = 0
|
||||
for (let pack of this.incompatiblePackages) {
|
||||
this.setPackageStatus(pack, REBUILDING)
|
||||
let {code, stderr} = await pack.rebuild()
|
||||
let { code, stderr } = await pack.rebuild()
|
||||
if (code === 0) {
|
||||
this.setPackageStatus(pack, REBUILD_SUCCEEDED)
|
||||
rebuiltPackageCount++
|
||||
@ -223,6 +234,6 @@ export default class IncompatiblePackagesComponent {
|
||||
}
|
||||
|
||||
serialize () {
|
||||
return {deserializer: 'IncompatiblePackagesComponent'}
|
||||
return { deserializer: 'IncompatiblePackagesComponent' }
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/** @babel */
|
||||
|
||||
import {Disposable, CompositeDisposable} from 'atom'
|
||||
import { Disposable, CompositeDisposable } from 'atom'
|
||||
import VIEW_URI from './view-uri'
|
||||
|
||||
let disposables = null
|
||||
@ -8,17 +8,21 @@ let disposables = null
|
||||
export function activate () {
|
||||
disposables = new CompositeDisposable()
|
||||
|
||||
disposables.add(atom.workspace.addOpener((uri) => {
|
||||
if (uri === VIEW_URI) {
|
||||
return deserializeIncompatiblePackagesComponent()
|
||||
}
|
||||
}))
|
||||
disposables.add(
|
||||
atom.workspace.addOpener(uri => {
|
||||
if (uri === VIEW_URI) {
|
||||
return deserializeIncompatiblePackagesComponent()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
disposables.add(atom.commands.add('atom-workspace', {
|
||||
'incompatible-packages:view': () => {
|
||||
atom.workspace.open(VIEW_URI)
|
||||
}
|
||||
}))
|
||||
disposables.add(
|
||||
atom.commands.add('atom-workspace', {
|
||||
'incompatible-packages:view': () => {
|
||||
atom.workspace.open(VIEW_URI)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function deactivate () {
|
||||
@ -33,7 +37,7 @@ export function consumeStatusBar (statusBar) {
|
||||
|
||||
if (incompatibleCount > 0) {
|
||||
let icon = createIcon(incompatibleCount)
|
||||
let tile = statusBar.addRightTile({item: icon, priority: 200})
|
||||
let tile = statusBar.addRightTile({ item: icon, priority: 200 })
|
||||
icon.element.addEventListener('click', () => {
|
||||
atom.commands.dispatch(icon.element, 'incompatible-packages:view')
|
||||
})
|
||||
@ -48,5 +52,5 @@ export function deserializeIncompatiblePackagesComponent () {
|
||||
|
||||
function createIcon (count) {
|
||||
const StatusIconComponent = require('./status-icon-component')
|
||||
return new StatusIconComponent({count})
|
||||
return new StatusIconComponent({ count })
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
import etch from 'etch'
|
||||
|
||||
export default class StatusIconComponent {
|
||||
constructor ({count}) {
|
||||
constructor ({ count }) {
|
||||
this.count = count
|
||||
etch.initialize(this)
|
||||
}
|
||||
@ -14,7 +14,7 @@ export default class StatusIconComponent {
|
||||
render () {
|
||||
return (
|
||||
<div className='incompatible-packages-status inline-block text text-error'>
|
||||
<span className='icon icon-bug'></span>
|
||||
<span className='icon icon-bug' />
|
||||
<span className='incompatible-packages-count'>{this.count}</span>
|
||||
</div>
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
return false
|
||||
},
|
||||
rebuild: function () {
|
||||
return new Promise((resolve) => this.resolveRebuild = resolve)
|
||||
return new Promise(resolve => (this.resolveRebuild = resolve))
|
||||
},
|
||||
getBuildFailureOutput () {
|
||||
return null
|
||||
@ -27,8 +27,8 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
version: '1.0.0'
|
||||
},
|
||||
incompatibleModules: [
|
||||
{name: 'x', version: '1.0.0', error: 'Expected version X, got Y'},
|
||||
{name: 'y', version: '1.0.0', error: 'Expected version X, got Z'}
|
||||
{ name: 'x', version: '1.0.0', error: 'Expected version X, got Y' },
|
||||
{ name: 'y', version: '1.0.0', error: 'Expected version X, got Z' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -37,7 +37,7 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
return false
|
||||
},
|
||||
rebuild () {
|
||||
return new Promise((resolve) => this.resolveRebuild = resolve)
|
||||
return new Promise(resolve => (this.resolveRebuild = resolve))
|
||||
},
|
||||
getBuildFailureOutput () {
|
||||
return null
|
||||
@ -48,7 +48,7 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
version: '1.0.0'
|
||||
},
|
||||
incompatibleModules: [
|
||||
{name: 'z', version: '1.0.0', error: 'Expected version X, got Y'}
|
||||
{ name: 'z', version: '1.0.0', error: 'Expected version X, got Y' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -67,7 +67,7 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
repository: 'https://github.com/atom/b',
|
||||
version: '1.0.0'
|
||||
},
|
||||
incompatibleModules: [],
|
||||
incompatibleModules: []
|
||||
}
|
||||
]
|
||||
})
|
||||
@ -75,18 +75,21 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
describe('when packages have not finished loading', () => {
|
||||
it('delays rendering incompatible packages until the end of the tick', () => {
|
||||
waitsForPromise(async () => {
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => [],
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => [],
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let { element } = component
|
||||
|
||||
expect(element.querySelectorAll('.incompatible-package').length).toEqual(0)
|
||||
expect(
|
||||
element.querySelectorAll('.incompatible-package').length
|
||||
).toEqual(0)
|
||||
|
||||
await etchScheduler.getNextUpdatePromise()
|
||||
|
||||
expect(element.querySelectorAll('.incompatible-package').length).toBeGreaterThan(0)
|
||||
expect(
|
||||
element.querySelectorAll('.incompatible-package').length
|
||||
).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -97,12 +100,11 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
expect(packages[2].isCompatible()).toBe(true)
|
||||
let compatiblePackages = [packages[2]]
|
||||
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => compatiblePackages,
|
||||
getLoadedPackages: () => compatiblePackages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => compatiblePackages,
|
||||
getLoadedPackages: () => compatiblePackages
|
||||
})
|
||||
let { element } = component
|
||||
|
||||
await etchScheduler.getNextUpdatePromise()
|
||||
|
||||
@ -119,18 +121,23 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
return 'The build failed'
|
||||
}
|
||||
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let { element } = component
|
||||
|
||||
await etchScheduler.getNextUpdatePromise()
|
||||
let packageElement = element.querySelector('.incompatible-package:nth-child(2)')
|
||||
let packageElement = element.querySelector(
|
||||
'.incompatible-package:nth-child(2)'
|
||||
)
|
||||
|
||||
expect(packageElement.querySelector('.badge').textContent).toBe('Rebuild Failed')
|
||||
expect(packageElement.querySelector('pre').textContent).toBe('The build failed')
|
||||
expect(packageElement.querySelector('.badge').textContent).toBe(
|
||||
'Rebuild Failed'
|
||||
)
|
||||
expect(packageElement.querySelector('pre').textContent).toBe(
|
||||
'The build failed'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -138,75 +145,101 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
describe('when there are incompatible packages', () => {
|
||||
it('renders incompatible packages and the rebuild button', () => {
|
||||
waitsForPromise(async () => {
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let { element } = component
|
||||
|
||||
await etchScheduler.getNextUpdatePromise()
|
||||
|
||||
expect(element.querySelectorAll('.incompatible-package').length).toEqual(2)
|
||||
expect(
|
||||
element.querySelectorAll('.incompatible-package').length
|
||||
).toEqual(2)
|
||||
expect(element.querySelector('button')).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the "Rebuild All" button is clicked', () => {
|
||||
it('rebuilds every incompatible package, updating each package\'s view with status', () => {
|
||||
it("rebuilds every incompatible package, updating each package's view with status", () => {
|
||||
waitsForPromise(async () => {
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let { element } = component
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
await etchScheduler.getNextUpdatePromise()
|
||||
|
||||
component.refs.rebuildButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
|
||||
component.refs.rebuildButton.dispatchEvent(
|
||||
new CustomEvent('click', { bubbles: true })
|
||||
)
|
||||
await etchScheduler.getNextUpdatePromise() // view update
|
||||
|
||||
expect(component.refs.rebuildButton.disabled).toBe(true)
|
||||
|
||||
expect(packages[0].resolveRebuild).toBeDefined()
|
||||
|
||||
expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuilding')
|
||||
expect(element.querySelector('.incompatible-package:nth-child(2) .badge')).toBeNull()
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(1) .badge')
|
||||
.textContent
|
||||
).toBe('Rebuilding')
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(2) .badge')
|
||||
).toBeNull()
|
||||
|
||||
packages[0].resolveRebuild({code: 0}) // simulate rebuild success
|
||||
packages[0].resolveRebuild({ code: 0 }) // simulate rebuild success
|
||||
await etchScheduler.getNextUpdatePromise() // view update
|
||||
|
||||
expect(packages[1].resolveRebuild).toBeDefined()
|
||||
|
||||
expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuild Succeeded')
|
||||
expect(element.querySelector('.incompatible-package:nth-child(2) .badge').textContent).toBe('Rebuilding')
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(1) .badge')
|
||||
.textContent
|
||||
).toBe('Rebuild Succeeded')
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(2) .badge')
|
||||
.textContent
|
||||
).toBe('Rebuilding')
|
||||
|
||||
packages[1].resolveRebuild({code: 12, stderr: 'This is an error from the test!'}) // simulate rebuild failure
|
||||
packages[1].resolveRebuild({
|
||||
code: 12,
|
||||
stderr: 'This is an error from the test!'
|
||||
}) // simulate rebuild failure
|
||||
await etchScheduler.getNextUpdatePromise() // view update
|
||||
|
||||
expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuild Succeeded')
|
||||
expect(element.querySelector('.incompatible-package:nth-child(2) .badge').textContent).toBe('Rebuild Failed')
|
||||
expect(element.querySelector('.incompatible-package:nth-child(2) pre').textContent).toBe('This is an error from the test!')
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(1) .badge')
|
||||
.textContent
|
||||
).toBe('Rebuild Succeeded')
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(2) .badge')
|
||||
.textContent
|
||||
).toBe('Rebuild Failed')
|
||||
expect(
|
||||
element.querySelector('.incompatible-package:nth-child(2) pre')
|
||||
.textContent
|
||||
).toBe('This is an error from the test!')
|
||||
})
|
||||
})
|
||||
|
||||
it('displays a prompt to reload Atom when the packages finish rebuilding', () => {
|
||||
waitsForPromise(async () => {
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let { element } = component
|
||||
jasmine.attachToDOM(element)
|
||||
await etchScheduler.getNextUpdatePromise() // view update
|
||||
|
||||
component.refs.rebuildButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
|
||||
expect(packages[0].resolveRebuild({code: 0}))
|
||||
component.refs.rebuildButton.dispatchEvent(
|
||||
new CustomEvent('click', { bubbles: true })
|
||||
)
|
||||
expect(packages[0].resolveRebuild({ code: 0 }))
|
||||
await new Promise(global.setImmediate)
|
||||
expect(packages[1].resolveRebuild({code: 0}))
|
||||
expect(packages[1].resolveRebuild({ code: 0 }))
|
||||
|
||||
await etchScheduler.getNextUpdatePromise() // view update
|
||||
|
||||
@ -214,7 +247,9 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/)
|
||||
|
||||
spyOn(atom, 'reload')
|
||||
component.refs.reloadButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
|
||||
component.refs.reloadButton.dispatchEvent(
|
||||
new CustomEvent('click', { bubbles: true })
|
||||
)
|
||||
expect(atom.reload).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@ -223,19 +258,22 @@ describe('IncompatiblePackagesComponent', () => {
|
||||
describe('when the "Package Settings" button is clicked', () => {
|
||||
it('opens the settings panel for the package', () => {
|
||||
waitsForPromise(async () => {
|
||||
let component =
|
||||
new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let {element} = component
|
||||
let component = new IncompatiblePackagesComponent({
|
||||
getActivePackages: () => packages,
|
||||
getLoadedPackages: () => packages
|
||||
})
|
||||
let { element } = component
|
||||
jasmine.attachToDOM(element)
|
||||
|
||||
await etchScheduler.getNextUpdatePromise()
|
||||
|
||||
spyOn(atom.workspace, 'open')
|
||||
element.querySelector('.incompatible-package:nth-child(2) button').dispatchEvent(new CustomEvent('click', {bubbles: true}))
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith('atom://config/packages/incompatible-2')
|
||||
element
|
||||
.querySelector('.incompatible-package:nth-child(2) button')
|
||||
.dispatchEvent(new CustomEvent('click', { bubbles: true }))
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(
|
||||
'atom://config/packages/incompatible-2'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -36,7 +36,9 @@ describe('Incompatible packages', () => {
|
||||
)
|
||||
spyOn(incompatiblePackage, 'isCompatible').andReturn(false)
|
||||
incompatiblePackage.incompatibleModules = []
|
||||
waitsForPromise(() => atom.packages.activatePackage("incompatible-packages"))
|
||||
waitsForPromise(() =>
|
||||
atom.packages.activatePackage('incompatible-packages')
|
||||
)
|
||||
|
||||
waits(1)
|
||||
})
|
||||
@ -63,13 +65,15 @@ describe('Incompatible packages', () => {
|
||||
|
||||
describe('when there are no packages with incompatible native modules', () => {
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => atom.packages.activatePackage("incompatible-packages"))
|
||||
waitsForPromise(() =>
|
||||
atom.packages.activatePackage('incompatible-packages')
|
||||
)
|
||||
})
|
||||
|
||||
it('does not add an icon to the status bar', () => {
|
||||
let statusBarItemClasses = statusBar
|
||||
.getRightTiles()
|
||||
.map((tile) => tile.getItem().className)
|
||||
.map(tile => tile.getItem().className)
|
||||
|
||||
expect(statusBarItemClasses).not.toContain('incompatible-packages')
|
||||
})
|
||||
|
@ -1,12 +1,17 @@
|
||||
'use babel'
|
||||
|
||||
import _ from 'underscore-plus'
|
||||
import {CompositeDisposable, Disposable} from 'atom'
|
||||
import { CompositeDisposable, Disposable } from 'atom'
|
||||
import SelectListView from 'atom-select-list'
|
||||
import StatusBarItem from './status-bar-item'
|
||||
import helpers from './helpers'
|
||||
|
||||
const LineEndingRegExp = /\r\n|\n/g
|
||||
|
||||
// the following regular expression is executed natively via the `substring` package,
|
||||
// where `\A` corresponds to the beginning of the string.
|
||||
// More info: https://github.com/atom/line-ending-selector/pull/56
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const LFRegExp = /(\A|[^\r])\n/g
|
||||
const CRLFRegExp = /\r\n/g
|
||||
|
||||
@ -17,48 +22,60 @@ let lineEndingListView = null
|
||||
export function activate () {
|
||||
disposables = new CompositeDisposable()
|
||||
|
||||
disposables.add(atom.commands.add('atom-text-editor', {
|
||||
'line-ending-selector:show': (event) => {
|
||||
if (!modalPanel) {
|
||||
lineEndingListView = new SelectListView({
|
||||
items: [{name: 'LF', value: '\n'}, {name: 'CRLF', value: '\r\n'}],
|
||||
filterKeyForItem: (lineEnding) => lineEnding.name,
|
||||
didConfirmSelection: (lineEnding) => {
|
||||
setLineEnding(atom.workspace.getActiveTextEditor(), lineEnding.value)
|
||||
modalPanel.hide()
|
||||
},
|
||||
didCancelSelection: () => {
|
||||
modalPanel.hide()
|
||||
},
|
||||
elementForItem: (lineEnding) => {
|
||||
const element = document.createElement('li')
|
||||
element.textContent = lineEnding.name
|
||||
return element
|
||||
}
|
||||
})
|
||||
modalPanel = atom.workspace.addModalPanel({item: lineEndingListView})
|
||||
disposables.add(new Disposable(() => {
|
||||
lineEndingListView.destroy()
|
||||
modalPanel.destroy()
|
||||
modalPanel = null
|
||||
}))
|
||||
disposables.add(
|
||||
atom.commands.add('atom-text-editor', {
|
||||
'line-ending-selector:show': event => {
|
||||
if (!modalPanel) {
|
||||
lineEndingListView = new SelectListView({
|
||||
items: [
|
||||
{ name: 'LF', value: '\n' },
|
||||
{ name: 'CRLF', value: '\r\n' }
|
||||
],
|
||||
filterKeyForItem: lineEnding => lineEnding.name,
|
||||
didConfirmSelection: lineEnding => {
|
||||
setLineEnding(
|
||||
atom.workspace.getActiveTextEditor(),
|
||||
lineEnding.value
|
||||
)
|
||||
modalPanel.hide()
|
||||
},
|
||||
didCancelSelection: () => {
|
||||
modalPanel.hide()
|
||||
},
|
||||
elementForItem: lineEnding => {
|
||||
const element = document.createElement('li')
|
||||
element.textContent = lineEnding.name
|
||||
return element
|
||||
}
|
||||
})
|
||||
modalPanel = atom.workspace.addModalPanel({
|
||||
item: lineEndingListView
|
||||
})
|
||||
disposables.add(
|
||||
new Disposable(() => {
|
||||
lineEndingListView.destroy()
|
||||
modalPanel.destroy()
|
||||
modalPanel = null
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
lineEndingListView.reset()
|
||||
modalPanel.show()
|
||||
lineEndingListView.focus()
|
||||
},
|
||||
|
||||
'line-ending-selector:convert-to-LF': event => {
|
||||
const editorElement = event.target.closest('atom-text-editor')
|
||||
setLineEnding(editorElement.getModel(), '\n')
|
||||
},
|
||||
|
||||
'line-ending-selector:convert-to-CRLF': event => {
|
||||
const editorElement = event.target.closest('atom-text-editor')
|
||||
setLineEnding(editorElement.getModel(), '\r\n')
|
||||
}
|
||||
|
||||
lineEndingListView.reset()
|
||||
modalPanel.show()
|
||||
lineEndingListView.focus()
|
||||
},
|
||||
|
||||
'line-ending-selector:convert-to-LF': (event) => {
|
||||
const editorElement = event.target.closest('atom-text-editor')
|
||||
setLineEnding(editorElement.getModel(), '\n')
|
||||
},
|
||||
|
||||
'line-ending-selector:convert-to-CRLF': (event) => {
|
||||
const editorElement = event.target.closest('atom-text-editor')
|
||||
setLineEnding(editorElement.getModel(), '\r\n')
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function deactivate () {
|
||||
@ -70,8 +87,8 @@ export function consumeStatusBar (statusBar) {
|
||||
let currentBufferDisposable = null
|
||||
let tooltipDisposable = null
|
||||
|
||||
const updateTile = _.debounce((buffer) => {
|
||||
getLineEndings(buffer).then((lineEndings) => {
|
||||
const updateTile = _.debounce(buffer => {
|
||||
getLineEndings(buffer).then(lineEndings => {
|
||||
if (lineEndings.size === 0) {
|
||||
let defaultLineEnding = getDefaultLineEnding()
|
||||
buffer.setPreferredLineEnding(defaultLineEnding)
|
||||
@ -81,45 +98,49 @@ export function consumeStatusBar (statusBar) {
|
||||
})
|
||||
}, 0)
|
||||
|
||||
disposables.add(atom.workspace.observeActiveTextEditor((editor) => {
|
||||
if (currentBufferDisposable) currentBufferDisposable.dispose()
|
||||
disposables.add(
|
||||
atom.workspace.observeActiveTextEditor(editor => {
|
||||
if (currentBufferDisposable) currentBufferDisposable.dispose()
|
||||
|
||||
if (editor && editor.getBuffer) {
|
||||
let buffer = editor.getBuffer()
|
||||
updateTile(buffer)
|
||||
currentBufferDisposable = buffer.onDidChange(({oldText, newText}) => {
|
||||
if (!statusBarItem.hasLineEnding('\n')) {
|
||||
if (newText.indexOf('\n') >= 0) {
|
||||
if (editor && editor.getBuffer) {
|
||||
let buffer = editor.getBuffer()
|
||||
updateTile(buffer)
|
||||
currentBufferDisposable = buffer.onDidChange(({ oldText, newText }) => {
|
||||
if (!statusBarItem.hasLineEnding('\n')) {
|
||||
if (newText.indexOf('\n') >= 0) {
|
||||
updateTile(buffer)
|
||||
}
|
||||
} else if (!statusBarItem.hasLineEnding('\r\n')) {
|
||||
if (newText.indexOf('\r\n') >= 0) {
|
||||
updateTile(buffer)
|
||||
}
|
||||
} else if (oldText.indexOf('\n')) {
|
||||
updateTile(buffer)
|
||||
}
|
||||
} else if (!statusBarItem.hasLineEnding('\r\n')) {
|
||||
if (newText.indexOf('\r\n') >= 0) {
|
||||
updateTile(buffer)
|
||||
}
|
||||
} else if (oldText.indexOf('\n')) {
|
||||
updateTile(buffer)
|
||||
})
|
||||
} else {
|
||||
statusBarItem.setLineEndings(new Set())
|
||||
currentBufferDisposable = null
|
||||
}
|
||||
|
||||
if (tooltipDisposable) {
|
||||
disposables.remove(tooltipDisposable)
|
||||
tooltipDisposable.dispose()
|
||||
}
|
||||
tooltipDisposable = atom.tooltips.add(statusBarItem.element, {
|
||||
title () {
|
||||
return `File uses ${statusBarItem.description()} line endings`
|
||||
}
|
||||
})
|
||||
} else {
|
||||
statusBarItem.setLineEndings(new Set())
|
||||
currentBufferDisposable = null
|
||||
}
|
||||
|
||||
if (tooltipDisposable) {
|
||||
disposables.remove(tooltipDisposable)
|
||||
tooltipDisposable.dispose()
|
||||
}
|
||||
tooltipDisposable = atom.tooltips.add(statusBarItem.element, {
|
||||
title () {
|
||||
return `File uses ${statusBarItem.description()} line endings`
|
||||
}
|
||||
disposables.add(tooltipDisposable)
|
||||
})
|
||||
disposables.add(tooltipDisposable)
|
||||
}))
|
||||
)
|
||||
|
||||
disposables.add(new Disposable(() => {
|
||||
if (currentBufferDisposable) currentBufferDisposable.dispose()
|
||||
}))
|
||||
disposables.add(
|
||||
new Disposable(() => {
|
||||
if (currentBufferDisposable) currentBufferDisposable.dispose()
|
||||
})
|
||||
)
|
||||
|
||||
statusBarItem.onClick(() => {
|
||||
const editor = atom.workspace.getActiveTextEditor()
|
||||
@ -129,7 +150,7 @@ export function consumeStatusBar (statusBar) {
|
||||
)
|
||||
})
|
||||
|
||||
let tile = statusBar.addRightTile({item: statusBarItem, priority: 200})
|
||||
let tile = statusBar.addRightTile({ item: statusBarItem, priority: 200 })
|
||||
disposables.add(new Disposable(() => tile.destroy()))
|
||||
}
|
||||
|
||||
@ -141,23 +162,22 @@ function getDefaultLineEnding () {
|
||||
return '\r\n'
|
||||
case 'OS Default':
|
||||
default:
|
||||
return (helpers.getProcessPlatform() === 'win32') ? '\r\n' : '\n'
|
||||
return helpers.getProcessPlatform() === 'win32' ? '\r\n' : '\n'
|
||||
}
|
||||
}
|
||||
|
||||
function getLineEndings (buffer) {
|
||||
if (typeof buffer.find === 'function') {
|
||||
return Promise.all([
|
||||
buffer.find(LFRegExp),
|
||||
buffer.find(CRLFRegExp)
|
||||
]).then(([hasLF, hasCRLF]) => {
|
||||
const result = new Set()
|
||||
if (hasLF) result.add('\n')
|
||||
if (hasCRLF) result.add('\r\n')
|
||||
return result
|
||||
})
|
||||
return Promise.all([buffer.find(LFRegExp), buffer.find(CRLFRegExp)]).then(
|
||||
([hasLF, hasCRLF]) => {
|
||||
const result = new Set()
|
||||
if (hasLF) result.add('\n')
|
||||
if (hasCRLF) result.add('\r\n')
|
||||
return result
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise(resolve => {
|
||||
const result = new Set()
|
||||
for (let i = 0; i < buffer.getLineCount() - 1; i++) {
|
||||
result.add(buffer.lineEndingForRow(i))
|
||||
|
@ -1,7 +1,6 @@
|
||||
const {Emitter} = require('atom')
|
||||
const { Emitter } = require('atom')
|
||||
|
||||
module.exports =
|
||||
class StatusBarItem {
|
||||
module.exports = class StatusBarItem {
|
||||
constructor () {
|
||||
this.element = document.createElement('a')
|
||||
this.element.className = 'line-ending-tile inline-block'
|
||||
@ -46,9 +45,13 @@ function lineEndingName (lineEndings) {
|
||||
|
||||
function lineEndingDescription (lineEndings) {
|
||||
switch (lineEndingName(lineEndings)) {
|
||||
case 'Mixed': return 'mixed'
|
||||
case 'LF': return 'LF (Unix)'
|
||||
case 'CRLF': return 'CRLF (Windows)'
|
||||
default: return 'unknown'
|
||||
case 'Mixed':
|
||||
return 'mixed'
|
||||
case 'LF':
|
||||
return 'LF (Unix)'
|
||||
case 'CRLF':
|
||||
return 'CRLF (Windows)'
|
||||
default:
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const helpers = require('../lib/helpers')
|
||||
const {TextEditor} = require('atom')
|
||||
const { TextEditor } = require('atom')
|
||||
|
||||
describe('line ending selector', () => {
|
||||
let lineEndingTile
|
||||
@ -30,7 +30,7 @@ describe('line ending selector', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => {
|
||||
return atom.workspace.open('mixed-endings.md').then((e) => {
|
||||
return atom.workspace.open('mixed-endings.md').then(e => {
|
||||
editor = e
|
||||
editorElement = atom.views.getView(editor)
|
||||
jasmine.attachToDOM(editorElement)
|
||||
@ -41,7 +41,10 @@ describe('line ending selector', () => {
|
||||
describe('When "line-ending-selector:convert-to-LF" is run', () => {
|
||||
it('converts the file to LF line endings', () => {
|
||||
editorElement.focus()
|
||||
atom.commands.dispatch(document.activeElement, 'line-ending-selector:convert-to-LF')
|
||||
atom.commands.dispatch(
|
||||
document.activeElement,
|
||||
'line-ending-selector:convert-to-LF'
|
||||
)
|
||||
expect(editor.getText()).toBe('Hello\nGoodbye\nMixed\n')
|
||||
})
|
||||
})
|
||||
@ -49,7 +52,10 @@ describe('line ending selector', () => {
|
||||
describe('When "line-ending-selector:convert-to-LF" is run', () => {
|
||||
it('converts the file to CRLF line endings', () => {
|
||||
editorElement.focus()
|
||||
atom.commands.dispatch(document.activeElement, 'line-ending-selector:convert-to-CRLF')
|
||||
atom.commands.dispatch(
|
||||
document.activeElement,
|
||||
'line-ending-selector:convert-to-CRLF'
|
||||
)
|
||||
expect(editor.getText()).toBe('Hello\r\nGoodbye\r\nMixed\r\n')
|
||||
})
|
||||
})
|
||||
@ -58,30 +64,34 @@ describe('line ending selector', () => {
|
||||
describe('Status bar tile', () => {
|
||||
describe('when an empty file is opened', () => {
|
||||
it('uses the default line endings for the platform', () => {
|
||||
waitsFor((done) => {
|
||||
waitsFor(done => {
|
||||
spyOn(helpers, 'getProcessPlatform').andReturn('win32')
|
||||
|
||||
atom.workspace.open('').then((editor) => {
|
||||
atom.workspace.open('').then(editor => {
|
||||
const subscription = lineEndingTile.onDidChange(() => {
|
||||
subscription.dispose()
|
||||
expect(lineEndingTile.element.textContent).toBe('CRLF')
|
||||
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe('File uses CRLF (Windows) line endings')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe(
|
||||
'File uses CRLF (Windows) line endings'
|
||||
)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
waitsFor((done) => {
|
||||
waitsFor(done => {
|
||||
helpers.getProcessPlatform.andReturn('darwin')
|
||||
|
||||
atom.workspace.open('').then((editor) => {
|
||||
atom.workspace.open('').then(editor => {
|
||||
const subscription = lineEndingTile.onDidChange(() => {
|
||||
subscription.dispose()
|
||||
expect(lineEndingTile.element.textContent).toBe('LF')
|
||||
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe('File uses LF (Unix) line endings')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe(
|
||||
'File uses LF (Unix) line endings'
|
||||
)
|
||||
|
||||
done()
|
||||
})
|
||||
@ -95,10 +105,10 @@ describe('line ending selector', () => {
|
||||
})
|
||||
|
||||
it('uses LF line endings, regardless of the platform', () => {
|
||||
waitsFor((done) => {
|
||||
waitsFor(done => {
|
||||
spyOn(helpers, 'getProcessPlatform').andReturn('win32')
|
||||
|
||||
atom.workspace.open('').then((editor) => {
|
||||
atom.workspace.open('').then(editor => {
|
||||
lineEndingTile.onDidChange(() => {
|
||||
expect(lineEndingTile.element.textContent).toBe('LF')
|
||||
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\n')
|
||||
@ -115,8 +125,8 @@ describe('line ending selector', () => {
|
||||
})
|
||||
|
||||
it('uses CRLF line endings, regardless of the platform', () => {
|
||||
waitsFor((done) => {
|
||||
atom.workspace.open('').then((editor) => {
|
||||
waitsFor(done => {
|
||||
atom.workspace.open('').then(editor => {
|
||||
lineEndingTile.onDidChange(() => {
|
||||
expect(lineEndingTile.element.textContent).toBe('CRLF')
|
||||
expect(editor.getBuffer().getPreferredLineEnding()).toBe('\r\n')
|
||||
@ -130,7 +140,7 @@ describe('line ending selector', () => {
|
||||
|
||||
describe('when a file is opened that contains only CRLF line endings', () => {
|
||||
it('displays "CRLF" as the line ending', () => {
|
||||
waitsFor((done) => {
|
||||
waitsFor(done => {
|
||||
atom.workspace.open('windows-endings.md').then(() => {
|
||||
lineEndingTile.onDidChange(() => {
|
||||
expect(lineEndingTile.element.textContent).toBe('CRLF')
|
||||
@ -143,8 +153,8 @@ describe('line ending selector', () => {
|
||||
|
||||
describe('when a file is opened that contains only LF line endings', () => {
|
||||
it('displays "LF" as the line ending', () => {
|
||||
waitsFor((done) => {
|
||||
atom.workspace.open('unix-endings.md').then((editor) => {
|
||||
waitsFor(done => {
|
||||
atom.workspace.open('unix-endings.md').then(editor => {
|
||||
lineEndingTile.onDidChange(() => {
|
||||
expect(lineEndingTile.element.textContent).toBe('LF')
|
||||
expect(editor.getBuffer().getPreferredLineEnding()).toBe(null)
|
||||
@ -157,7 +167,7 @@ describe('line ending selector', () => {
|
||||
|
||||
describe('when a file is opened that contains mixed line endings', () => {
|
||||
it('displays "Mixed" as the line ending', () => {
|
||||
waitsFor((done) => {
|
||||
waitsFor(done => {
|
||||
atom.workspace.open('mixed-endings.md').then(() => {
|
||||
lineEndingTile.onDidChange(() => {
|
||||
expect(lineEndingTile.element.textContent).toBe('Mixed')
|
||||
@ -174,10 +184,10 @@ describe('line ending selector', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.attachToDOM(atom.views.getView(atom.workspace))
|
||||
|
||||
waitsFor((done) =>
|
||||
atom.workspace.open('unix-endings.md').then(() =>
|
||||
lineEndingTile.onDidChange(done)
|
||||
)
|
||||
waitsFor(done =>
|
||||
atom.workspace
|
||||
.open('unix-endings.md')
|
||||
.then(() => lineEndingTile.onDidChange(done))
|
||||
)
|
||||
})
|
||||
|
||||
@ -192,7 +202,9 @@ describe('line ending selector', () => {
|
||||
lineEndingSelector = lineEndingModal.getItem()
|
||||
|
||||
expect(lineEndingModal.isVisible()).toBe(true)
|
||||
expect(lineEndingSelector.element.contains(document.activeElement)).toBe(true)
|
||||
expect(
|
||||
lineEndingSelector.element.contains(document.activeElement)
|
||||
).toBe(true)
|
||||
let listItems = lineEndingSelector.element.querySelectorAll('li')
|
||||
expect(listItems[0].textContent).toBe('LF')
|
||||
expect(listItems[1].textContent).toBe('CRLF')
|
||||
@ -210,7 +222,9 @@ describe('line ending selector', () => {
|
||||
lineEndingSelector = lineEndingModal.getItem()
|
||||
|
||||
expect(lineEndingModal.isVisible()).toBe(true)
|
||||
expect(lineEndingSelector.element.contains(document.activeElement)).toBe(true)
|
||||
expect(
|
||||
lineEndingSelector.element.contains(document.activeElement)
|
||||
).toBe(true)
|
||||
let listItems = lineEndingSelector.element.querySelectorAll('li')
|
||||
expect(listItems[0].textContent).toBe('LF')
|
||||
expect(listItems[1].textContent).toBe('CRLF')
|
||||
@ -264,12 +278,12 @@ describe('line ending selector', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the buffer\'s line endings change', () => {
|
||||
describe("when the buffer's line endings change", () => {
|
||||
let editor
|
||||
|
||||
beforeEach(() => {
|
||||
waitsFor((done) => {
|
||||
atom.workspace.open('unix-endings.md').then((e) => {
|
||||
waitsFor(done => {
|
||||
atom.workspace.open('unix-endings.md').then(e => {
|
||||
editor = e
|
||||
lineEndingTile.onDidChange(done)
|
||||
})
|
||||
@ -291,33 +305,47 @@ describe('line ending selector', () => {
|
||||
})
|
||||
|
||||
expect(lineEndingTile.element.textContent).toBe('LF')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe('File uses LF (Unix) line endings')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe(
|
||||
'File uses LF (Unix) line endings'
|
||||
)
|
||||
|
||||
waitsFor((done) => {
|
||||
waitsFor(done => {
|
||||
editor.setTextInBufferRange([[0, 0], [0, 0]], '... ')
|
||||
editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', {normalizeLineEndings: false})
|
||||
editor.setTextInBufferRange([[0, Infinity], [1, 0]], '\r\n', {
|
||||
normalizeLineEndings: false
|
||||
})
|
||||
lineEndingTile.onDidChange(done)
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
expect(tileUpdateCount).toBe(1)
|
||||
expect(lineEndingTile.element.textContent).toBe('Mixed')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe('File uses mixed line endings')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe(
|
||||
'File uses mixed line endings'
|
||||
)
|
||||
})
|
||||
|
||||
waitsFor((done) => {
|
||||
atom.commands.dispatch(editor.getElement(), 'line-ending-selector:convert-to-CRLF')
|
||||
waitsFor(done => {
|
||||
atom.commands.dispatch(
|
||||
editor.getElement(),
|
||||
'line-ending-selector:convert-to-CRLF'
|
||||
)
|
||||
lineEndingTile.onDidChange(done)
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
expect(tileUpdateCount).toBe(2)
|
||||
expect(lineEndingTile.element.textContent).toBe('CRLF')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe('File uses CRLF (Windows) line endings')
|
||||
expect(getTooltipText(lineEndingTile.element)).toBe(
|
||||
'File uses CRLF (Windows) line endings'
|
||||
)
|
||||
})
|
||||
|
||||
waitsFor((done) => {
|
||||
atom.commands.dispatch(editor.getElement(), 'line-ending-selector:convert-to-LF')
|
||||
waitsFor(done => {
|
||||
atom.commands.dispatch(
|
||||
editor.getElement(),
|
||||
'line-ending-selector:convert-to-LF'
|
||||
)
|
||||
lineEndingTile.onDidChange(done)
|
||||
})
|
||||
|
||||
|
@ -1,12 +1,16 @@
|
||||
const url = require('url')
|
||||
const {shell} = require('electron')
|
||||
const { shell } = require('electron')
|
||||
const _ = require('underscore-plus')
|
||||
|
||||
const LINK_SCOPE_REGEX = /markup\.underline\.link/
|
||||
|
||||
module.exports = {
|
||||
activate () {
|
||||
this.commandDisposable = atom.commands.add('atom-text-editor', 'link:open', () => this.openLink())
|
||||
this.commandDisposable = atom.commands.add(
|
||||
'atom-text-editor',
|
||||
'link:open',
|
||||
() => this.openLink()
|
||||
)
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
@ -24,8 +28,10 @@ module.exports = {
|
||||
link = this.linkForName(editor, link)
|
||||
}
|
||||
|
||||
const {protocol} = url.parse(link)
|
||||
if (protocol === 'http:' || protocol === 'https:' || protocol === 'atom:') shell.openExternal(link)
|
||||
const { protocol } = url.parse(link)
|
||||
if (protocol === 'http:' || protocol === 'https:' || protocol === 'atom:') {
|
||||
shell.openExternal(link)
|
||||
}
|
||||
},
|
||||
|
||||
// Get the link under the cursor in the editor
|
||||
@ -47,7 +53,11 @@ module.exports = {
|
||||
// Returns a {String} link or undefined if no link found.
|
||||
linkAtPosition (editor, bufferPosition) {
|
||||
const token = editor.tokenForBufferPosition(bufferPosition)
|
||||
if (token && token.value && token.scopes.some(scope => LINK_SCOPE_REGEX.test(scope))) {
|
||||
if (
|
||||
token &&
|
||||
token.value &&
|
||||
token.scopes.some(scope => LINK_SCOPE_REGEX.test(scope))
|
||||
) {
|
||||
return token.value
|
||||
}
|
||||
},
|
||||
@ -65,11 +75,18 @@ module.exports = {
|
||||
// Returns a {String} link
|
||||
linkForName (editor, linkName) {
|
||||
let link = linkName
|
||||
const regex = new RegExp(`^\\s*\\[${_.escapeRegExp(linkName)}\\]\\s*:\\s*(.+)$`, 'g')
|
||||
editor.backwardsScanInBufferRange(regex, [[0, 0], [Infinity, Infinity]], ({match, stop}) => {
|
||||
link = match[1]
|
||||
stop()
|
||||
})
|
||||
const regex = new RegExp(
|
||||
`^\\s*\\[${_.escapeRegExp(linkName)}\\]\\s*:\\s*(.+)$`,
|
||||
'g'
|
||||
)
|
||||
editor.backwardsScanInBufferRange(
|
||||
regex,
|
||||
[[0, 0], [Infinity, Infinity]],
|
||||
({ match, stop }) => {
|
||||
link = match[1]
|
||||
stop()
|
||||
}
|
||||
)
|
||||
return link
|
||||
}
|
||||
}
|
||||
|
@ -1,103 +0,0 @@
|
||||
/** @babel */
|
||||
|
||||
export function beforeEach (fn) {
|
||||
global.beforeEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function afterEach (fn) {
|
||||
global.afterEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
module.exports[name] = function (description, fn) {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export async function conditionPromise (condition, description = 'anonymous condition') {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
await timeoutPromise(100)
|
||||
|
||||
if (await condition()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > 5000) {
|
||||
throw new Error('Timed out waiting on ' + description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function timeoutPromise (timeout) {
|
||||
return new Promise(function (resolve) {
|
||||
global.setTimeout(resolve, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
function waitsForPromise (fn) {
|
||||
const promise = fn()
|
||||
global.waitsFor('spec promise to resolve', function (done) {
|
||||
promise.then(done, function (error) {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`Timed out waiting for '${event}' event`))
|
||||
}, timeout)
|
||||
emitter.once(event, () => {
|
||||
clearTimeout(timeoutHandle)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function promisify (original) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, ...results) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(...results)
|
||||
}
|
||||
})
|
||||
|
||||
return original(...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function promisifySome (obj, fnNames) {
|
||||
const result = {}
|
||||
for (const fnName of fnNames) {
|
||||
result[fnName] = promisify(obj[fnName])
|
||||
}
|
||||
return result
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
const {shell} = require('electron')
|
||||
|
||||
const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
const { shell } = require('electron')
|
||||
|
||||
describe('link package', () => {
|
||||
beforeEach(async () => {
|
||||
@ -47,13 +45,15 @@ describe('link package', () => {
|
||||
// only works in Atom >= 1.33.0
|
||||
// https://github.com/atom/link/pull/33#issuecomment-419643655
|
||||
const atomVersion = atom.getVersion().split('.')
|
||||
console.error("atomVersion", atomVersion)
|
||||
console.error('atomVersion', atomVersion)
|
||||
if (+atomVersion[0] > 1 || +atomVersion[1] >= 33) {
|
||||
it("opens an 'atom:' link", async () => {
|
||||
await atom.workspace.open('sample.md')
|
||||
|
||||
const editor = atom.workspace.getActiveTextEditor()
|
||||
editor.setText('// "atom://core/open/file?filename=sample.js&line=1&column=2"')
|
||||
editor.setText(
|
||||
'// "atom://core/open/file?filename=sample.js&line=1&column=2"'
|
||||
)
|
||||
|
||||
spyOn(shell, 'openExternal')
|
||||
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
|
||||
@ -63,21 +63,27 @@ describe('link package', () => {
|
||||
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
|
||||
|
||||
expect(shell.openExternal).toHaveBeenCalled()
|
||||
expect(shell.openExternal.argsForCall[0][0]).toBe('atom://core/open/file?filename=sample.js&line=1&column=2')
|
||||
expect(shell.openExternal.argsForCall[0][0]).toBe(
|
||||
'atom://core/open/file?filename=sample.js&line=1&column=2'
|
||||
)
|
||||
|
||||
shell.openExternal.reset()
|
||||
editor.setCursorBufferPosition([0, 8])
|
||||
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
|
||||
|
||||
expect(shell.openExternal).toHaveBeenCalled()
|
||||
expect(shell.openExternal.argsForCall[0][0]).toBe('atom://core/open/file?filename=sample.js&line=1&column=2')
|
||||
expect(shell.openExternal.argsForCall[0][0]).toBe(
|
||||
'atom://core/open/file?filename=sample.js&line=1&column=2'
|
||||
)
|
||||
|
||||
shell.openExternal.reset()
|
||||
editor.setCursorBufferPosition([0, 60])
|
||||
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
|
||||
|
||||
expect(shell.openExternal).toHaveBeenCalled()
|
||||
expect(shell.openExternal.argsForCall[0][0]).toBe('atom://core/open/file?filename=sample.js&line=1&column=2')
|
||||
expect(shell.openExternal.argsForCall[0][0]).toBe(
|
||||
'atom://core/open/file?filename=sample.js&line=1&column=2'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -91,8 +97,7 @@ you should [click][here]
|
||||
you should not [click][her]
|
||||
|
||||
[here]: http://github.com\
|
||||
`
|
||||
)
|
||||
`)
|
||||
|
||||
spyOn(shell, 'openExternal')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
@ -110,8 +115,7 @@ you should not [click][her]
|
||||
atom.commands.dispatch(atom.views.getView(editor), 'link:open')
|
||||
|
||||
expect(shell.openExternal).not.toHaveBeenCalled()
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
it('does not open non http/https/atom links', async () => {
|
||||
await atom.workspace.open('sample.md')
|
||||
|
@ -14,31 +14,45 @@ describe(`${themeName} theme`, () => {
|
||||
|
||||
it('allows the tab sizing to be set via config', () => {
|
||||
atom.config.set(`${themeName}.tabSizing`, 'Maximum')
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('maximum')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
|
||||
).toBe('maximum')
|
||||
})
|
||||
|
||||
it('allows the tab sizing to be set via config', () => {
|
||||
atom.config.set(`${themeName}.tabSizing`, 'Minimum')
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('minimum')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
|
||||
).toBe('minimum')
|
||||
})
|
||||
|
||||
it('allows the tab close button to be shown on the left via config', () => {
|
||||
atom.config.set(`${themeName}.tabCloseButton`, 'Left')
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-tab-close-button`)).toBe('left')
|
||||
expect(
|
||||
document.documentElement.getAttribute(
|
||||
`theme-${themeName}-tab-close-button`
|
||||
)
|
||||
).toBe('left')
|
||||
})
|
||||
|
||||
it('allows the dock toggle buttons to be hidden via config', () => {
|
||||
atom.config.set(`${themeName}.hideDockButtons`, true)
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)).toBe('hidden')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)
|
||||
).toBe('hidden')
|
||||
})
|
||||
|
||||
it('allows the tree-view headers to be sticky via config', () => {
|
||||
atom.config.set(`${themeName}.stickyHeaders`, true)
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe('sticky')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
|
||||
).toBe('sticky')
|
||||
})
|
||||
|
||||
it('allows the tree-view headers to not be sticky via config', () => {
|
||||
atom.config.set(`${themeName}.stickyHeaders`, false)
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe(null)
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
|
||||
).toBe(null)
|
||||
})
|
||||
})
|
||||
|
@ -14,31 +14,45 @@ describe(`${themeName} theme`, () => {
|
||||
|
||||
it('allows the tab sizing to be set via config', () => {
|
||||
atom.config.set(`${themeName}.tabSizing`, 'Maximum')
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('maximum')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
|
||||
).toBe('maximum')
|
||||
})
|
||||
|
||||
it('allows the tab sizing to be set via config', () => {
|
||||
atom.config.set(`${themeName}.tabSizing`, 'Minimum')
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)).toBe('minimum')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-tabsizing`)
|
||||
).toBe('minimum')
|
||||
})
|
||||
|
||||
it('allows the tab close button to be shown on the left via config', () => {
|
||||
atom.config.set(`${themeName}.tabCloseButton`, 'Left')
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-tab-close-button`)).toBe('left')
|
||||
expect(
|
||||
document.documentElement.getAttribute(
|
||||
`theme-${themeName}-tab-close-button`
|
||||
)
|
||||
).toBe('left')
|
||||
})
|
||||
|
||||
it('allows the dock toggle buttons to be hidden via config', () => {
|
||||
atom.config.set(`${themeName}.hideDockButtons`, true)
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)).toBe('hidden')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-dock-buttons`)
|
||||
).toBe('hidden')
|
||||
})
|
||||
|
||||
it('allows the tree-view headers to be sticky via config', () => {
|
||||
atom.config.set(`${themeName}.stickyHeaders`, true)
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe('sticky')
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
|
||||
).toBe('sticky')
|
||||
})
|
||||
|
||||
it('allows the tree-view headers to not be sticky via config', () => {
|
||||
atom.config.set(`${themeName}.stickyHeaders`, false)
|
||||
expect(document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)).toBe(null)
|
||||
expect(
|
||||
document.documentElement.getAttribute(`theme-${themeName}-sticky-headers`)
|
||||
).toBe(null)
|
||||
})
|
||||
})
|
||||
|
@ -5,17 +5,17 @@ const CONFIG = require('../config')
|
||||
const semver = require('semver')
|
||||
|
||||
module.exports = function () {
|
||||
// Chromedriver should be specified as ^n.x where n matches the Electron major version
|
||||
// Chromedriver should be specified as ~x.y where x and y match Electron major/minor
|
||||
const chromedriverVer = buildMetadata.dependencies['electron-chromedriver']
|
||||
const mksnapshotVer = buildMetadata.dependencies['electron-mksnapshot']
|
||||
|
||||
// Always use caret on electron-chromedriver so that it can pick up the best minor/patch versions
|
||||
if (!chromedriverVer.startsWith('^')) {
|
||||
throw new Error(`electron-chromedriver version in script/package.json should start with a caret to match latest patch version.`)
|
||||
// Always use tilde on electron-chromedriver so that it can pick up the best patch version
|
||||
if (!chromedriverVer.startsWith('~')) {
|
||||
throw new Error(`electron-chromedriver version in script/package.json should start with a tilde to match latest patch version.`)
|
||||
}
|
||||
|
||||
if (!mksnapshotVer.startsWith('^')) {
|
||||
throw new Error(`electron-mksnapshot version in script/package.json should start with a caret to match latest patch version.`)
|
||||
if (!mksnapshotVer.startsWith('~')) {
|
||||
throw new Error(`electron-mksnapshot version in script/package.json should start with a tilde to match latest patch version.`)
|
||||
}
|
||||
|
||||
const electronVer = CONFIG.appMetadata.electronVersion
|
||||
|
@ -35,7 +35,7 @@ function dumpSymbol (binaryPath) {
|
||||
const symbolDirPath = path.join(CONFIG.symbolsPath, filename, moduleLine[1])
|
||||
const symbolFilePath = path.join(symbolDirPath, `${filename}.sym`)
|
||||
fs.mkdirpSync(symbolDirPath)
|
||||
fs.writeFileSync(symbolFilePath)
|
||||
fs.writeFileSync(symbolFilePath, content)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ module.exports = function (packagedAppPath) {
|
||||
requiredModuleRelativePath === path.join('..', 'src', 'electron-shims.js') ||
|
||||
requiredModuleRelativePath === path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') ||
|
||||
requiredModuleRelativePath === path.join('..', 'node_modules', 'babel-core', 'index.js') ||
|
||||
requiredModuleRelativePath === path.join('..', 'node_modules', 'cached-run-in-this-context', 'lib', 'main.js') ||
|
||||
requiredModuleRelativePath === path.join('..', 'node_modules', 'debug', 'node.js') ||
|
||||
requiredModuleRelativePath === path.join('..', 'node_modules', 'git-utils', 'src', 'git.js') ||
|
||||
requiredModuleRelativePath === path.join('..', 'node_modules', 'glob', 'glob.js') ||
|
||||
@ -96,36 +97,22 @@ module.exports = function (packagedAppPath) {
|
||||
{env: Object.assign({}, process.env, {ELECTRON_RUN_AS_NODE: 1})}
|
||||
)
|
||||
|
||||
console.log('Generating startup blob with mksnapshot')
|
||||
childProcess.spawnSync(
|
||||
process.execPath, [
|
||||
path.join(CONFIG.repositoryRootPath, 'script', 'node_modules', 'electron-mksnapshot', 'mksnapshot.js'),
|
||||
snapshotScriptPath,
|
||||
'--output_dir',
|
||||
CONFIG.buildOutputPath
|
||||
]
|
||||
const generatedStartupBlobPath = path.join(CONFIG.buildOutputPath, 'snapshot_blob.bin')
|
||||
console.log(`Generating startup blob at "${generatedStartupBlobPath}"`)
|
||||
childProcess.execFileSync(
|
||||
path.join(CONFIG.repositoryRootPath, 'script', 'node_modules', 'electron-mksnapshot', 'bin', 'mksnapshot'),
|
||||
['--no-use_ic', snapshotScriptPath, '--startup_blob', generatedStartupBlobPath]
|
||||
)
|
||||
|
||||
let startupBlobDestinationPath
|
||||
if (process.platform === 'darwin') {
|
||||
startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources`
|
||||
startupBlobDestinationPath = `${packagedAppPath}/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin`
|
||||
} else {
|
||||
startupBlobDestinationPath = packagedAppPath
|
||||
startupBlobDestinationPath = path.join(packagedAppPath, 'snapshot_blob.bin')
|
||||
}
|
||||
|
||||
const snapshotBinaries = ['v8_context_snapshot.bin', 'snapshot_blob.bin']
|
||||
for (let snapshotBinary of snapshotBinaries) {
|
||||
const destinationPath = path.join(startupBlobDestinationPath, snapshotBinary)
|
||||
console.log(`Moving generated startup blob into "${destinationPath}"`)
|
||||
try {
|
||||
fs.unlinkSync(destinationPath)
|
||||
} catch (err) {
|
||||
// Doesn't matter if the file doesn't exist already
|
||||
if (!err.code || err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
fs.renameSync(path.join(CONFIG.buildOutputPath, snapshotBinary), destinationPath)
|
||||
}
|
||||
console.log(`Moving generated startup blob into "${startupBlobDestinationPath}"`)
|
||||
fs.unlinkSync(startupBlobDestinationPath)
|
||||
fs.renameSync(generatedStartupBlobPath, startupBlobDestinationPath)
|
||||
})
|
||||
}
|
||||
|
@ -6,28 +6,46 @@ const path = require('path')
|
||||
|
||||
const CONFIG = require('../config')
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = async function () {
|
||||
const globPathsToLint = [
|
||||
path.join(CONFIG.repositoryRootPath, 'exports', '**', '*.js'),
|
||||
path.join(CONFIG.repositoryRootPath, 'packages', '**', '*.js'),
|
||||
path.join(CONFIG.repositoryRootPath, 'script', '**', '*.js'),
|
||||
path.join(CONFIG.repositoryRootPath, 'spec', '**', '*.js'),
|
||||
path.join(CONFIG.repositoryRootPath, 'src', '**', '*.js'),
|
||||
path.join(CONFIG.repositoryRootPath, 'static', '*.js')
|
||||
]
|
||||
return expandGlobPaths(globPathsToLint).then((paths) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
standard.lintFiles(paths, (error, lintOutput) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
const errors = []
|
||||
for (let result of lintOutput.results) {
|
||||
for (let message of result.messages) {
|
||||
errors.push({path: result.filePath, lineNumber: message.line, message: message.message, rule: message.ruleId})
|
||||
}
|
||||
const globPathsToIgnore = [
|
||||
path.join(CONFIG.repositoryRootPath, 'spec', 'fixtures', '**', '*.js')
|
||||
]
|
||||
|
||||
const [includePaths, excludePaths] = await Promise.all([
|
||||
expandGlobPaths(globPathsToLint),
|
||||
expandGlobPaths(globPathsToIgnore)
|
||||
])
|
||||
|
||||
const paths = includePaths.filter(
|
||||
myPath => !excludePaths.includes(myPath)
|
||||
)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
standard.lintFiles(paths, (error, lintOutput) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
const errors = []
|
||||
for (let result of lintOutput.results) {
|
||||
for (let message of result.messages) {
|
||||
errors.push({
|
||||
path: result.filePath,
|
||||
lineNumber: message.line,
|
||||
message: message.message,
|
||||
rule: message.ruleId
|
||||
})
|
||||
}
|
||||
resolve(errors)
|
||||
}
|
||||
})
|
||||
resolve(errors)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
86
script/package-lock.json
generated
86
script/package-lock.json
generated
@ -1692,72 +1692,50 @@
|
||||
}
|
||||
},
|
||||
"electron-chromedriver": {
|
||||
"version": "3.0.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-3.0.0-beta.1.tgz",
|
||||
"integrity": "sha512-S8KuOWqTISSfeVccrh1XjqR5tARdkAbF93azz8TvuNJTKoIw7V54mLKoyhi2Hj5UvKuPLcrHmfa4B9Uh45A5lA==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-2.0.0.tgz",
|
||||
"integrity": "sha512-kERk/Wzhc9RzW9jUKXA5kJc4m8BlL6c9p5QH+CrIlst0saeqZL1Up7vzD4ZOnuBDpAVBBYJ4jhkAKIssf8ZlXg==",
|
||||
"requires": {
|
||||
"electron-download": "^4.1.0",
|
||||
"extract-zip": "^1.6.5"
|
||||
}
|
||||
},
|
||||
"electron-download": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz",
|
||||
"integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.0.tgz",
|
||||
"integrity": "sha1-v5MsdG8vh//MCdHdRy8v9rkYeEU=",
|
||||
"requires": {
|
||||
"debug": "^3.0.0",
|
||||
"debug": "^2.2.0",
|
||||
"env-paths": "^1.0.0",
|
||||
"fs-extra": "^4.0.1",
|
||||
"fs-extra": "^2.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"nugget": "^2.0.1",
|
||||
"nugget": "^2.0.0",
|
||||
"path-exists": "^3.0.0",
|
||||
"rc": "^1.2.1",
|
||||
"semver": "^5.4.1",
|
||||
"sumchecker": "^2.0.2"
|
||||
"rc": "^1.1.2",
|
||||
"semver": "^5.3.0",
|
||||
"sumchecker": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
|
||||
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz",
|
||||
"integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
"jsonfile": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
|
||||
"integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-link": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.3.3.tgz",
|
||||
"integrity": "sha512-dxFY3o3E9kBkOyfaY66PWabK1AL5Re8qmy2Abz2/VhVkp2KtvUn6BZODTm9XpC0REgWxlQfRyHlNTlXRBPrWCQ==",
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.3.2.tgz",
|
||||
"integrity": "sha512-V7QmtujzWgvrW5BI2CKmIRF+q+pkrFO5Lecd8TpibbBz+FfW5WQ4kCN0sZjNaUOMtGGroCib721OqIDEynjwgA==",
|
||||
"requires": {
|
||||
"ast-util": "^0.6.0",
|
||||
"encoding-down": "~5.0.0",
|
||||
@ -1775,9 +1753,9 @@
|
||||
"integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ=="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.3.tgz",
|
||||
"integrity": "sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ=="
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
|
||||
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
@ -1811,13 +1789,12 @@
|
||||
}
|
||||
},
|
||||
"electron-mksnapshot": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/electron-mksnapshot/-/electron-mksnapshot-3.0.10.tgz",
|
||||
"integrity": "sha512-Toy6sAC3t9tgvq1kUYsx+4TRNPDj7Bzoo+1gx5FD8Q0YCS+tq+ter62Ot6dBXCKG9SwoaGBz84b++MgO0VobYw==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-mksnapshot/-/electron-mksnapshot-2.0.0.tgz",
|
||||
"integrity": "sha512-OoZwZJNKgHP+DwhCGVTJEuDSeb478hOzAbHeg7dKGCHDbKKmUWmjGc+pEjxGutpqQ3Mn8hCdLzdx2c/lAJcTLA==",
|
||||
"requires": {
|
||||
"electron-download": "^4.1.0",
|
||||
"extract-zip": "^1.6.5",
|
||||
"temp": "^0.8.3"
|
||||
"extract-zip": "^1.6.5"
|
||||
}
|
||||
},
|
||||
"electron-osx-sign": {
|
||||
@ -4598,9 +4575,9 @@
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.0.tgz",
|
||||
"integrity": "sha512-egTtvNoZLMjwxkL/5iiJKYKZgn2im0zP+G+PncMxICYGiD3aZtXUvEsDmu0pF8gpASvLZyD8v53qi1/ELaRZpg==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz",
|
||||
"integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==",
|
||||
"requires": {
|
||||
"semver": "^5.4.1"
|
||||
},
|
||||
@ -10085,11 +10062,6 @@
|
||||
"unist-util-is": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
@ -9,9 +9,9 @@
|
||||
"coffeelint": "1.15.7",
|
||||
"colors": "1.1.2",
|
||||
"donna": "1.0.16",
|
||||
"electron-chromedriver": "^3.0.0-beta.1",
|
||||
"electron-link": "0.3.3",
|
||||
"electron-mksnapshot": "^3.0.10",
|
||||
"electron-chromedriver": "~2.0",
|
||||
"electron-link": "0.3.2",
|
||||
"electron-mksnapshot": "~2.0",
|
||||
"electron-packager": "7.3.0",
|
||||
"electron-winstaller": "2.6.4",
|
||||
"fs-admin": "^0.1.5",
|
||||
|
@ -116,6 +116,15 @@ jobs:
|
||||
displayName: Upload atom-x64-$(ReleaseVersion)-full.nupkg
|
||||
condition: and(succeeded(), eq(variables['IsReleaseBranch'], 'true'), eq(variables['buildArch'], 'x64'))
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: $(Build.SourcesDirectory)/out/atom-x64-$(ReleaseVersion)-delta.nupkg
|
||||
ArtifactName: atom-x64-$(ReleaseVersion)-delta.nupkg
|
||||
ArtifactType: Container
|
||||
displayName: Upload atom-x64-$(ReleaseVersion)-delta.nupkg
|
||||
condition: and(succeeded(), eq(variables['IsReleaseBranch'], 'true'), eq(variables['buildArch'], 'x64'))
|
||||
continueOnError: true # Nightly builds don't produce delta packages yet, so don't fail the build
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: $(Build.SourcesDirectory)/out/RELEASES-x64
|
||||
@ -148,6 +157,15 @@ jobs:
|
||||
displayName: Upload atom-$(ReleaseVersion)-full.nupkg
|
||||
condition: and(succeeded(), eq(variables['IsReleaseBranch'], 'true'), eq(variables['buildArch'], 'x86'))
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: $(Build.SourcesDirectory)/out/atom-$(ReleaseVersion)-delta.nupkg
|
||||
ArtifactName: atom-$(ReleaseVersion)-delta.nupkg
|
||||
ArtifactType: Container
|
||||
displayName: Upload atom-$(ReleaseVersion)-delta.nupkg
|
||||
condition: and(succeeded(), eq(variables['IsReleaseBranch'], 'true'), eq(variables['buildArch'], 'x86'))
|
||||
continueOnError: true # Nightly builds don't produce delta packages yet, so don't fail the build
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: $(Build.SourcesDirectory)/out/RELEASES
|
||||
|
@ -1,6 +1,7 @@
|
||||
trigger:
|
||||
- master
|
||||
- 1.* # VSTS only supports wildcards at the end
|
||||
- electron-*
|
||||
|
||||
resources:
|
||||
containers:
|
||||
|
@ -1,14 +1,13 @@
|
||||
/** @babel */
|
||||
|
||||
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
|
||||
import ApplicationDelegate from '../src/application-delegate'
|
||||
|
||||
describe('ApplicationDelegate', function () {
|
||||
describe('set/getTemporaryWindowState', function () {
|
||||
it('can serialize object trees containing redundant child object references', async function () {
|
||||
const applicationDelegate = new ApplicationDelegate()
|
||||
const childObject = {c: 1}
|
||||
const sentObject = {a: childObject, b: childObject}
|
||||
const childObject = { c: 1 }
|
||||
const sentObject = { a: childObject, b: childObject }
|
||||
|
||||
await applicationDelegate.setTemporaryWindowState(sentObject)
|
||||
const receivedObject = await applicationDelegate.getTemporaryWindowState()
|
||||
|
@ -1,38 +1,7 @@
|
||||
function beforeEach (fn) {
|
||||
global.beforeEach(() => {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function afterEach (fn) {
|
||||
global.afterEach(() => {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
;['it', 'fit', 'ffit', 'fffit'].forEach(name => {
|
||||
exports[name] = (description, fn) => {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, () => {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
async function conditionPromise (condition, description = 'anonymous condition') {
|
||||
async function conditionPromise (
|
||||
condition,
|
||||
description = 'anonymous condition'
|
||||
) {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
@ -54,16 +23,6 @@ function timeoutPromise (timeout) {
|
||||
})
|
||||
}
|
||||
|
||||
function waitsForPromise (fn) {
|
||||
const promise = fn()
|
||||
global.waitsFor('spec promise to resolve', done => {
|
||||
promise.then(done, error => {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
@ -76,34 +35,6 @@ function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
})
|
||||
}
|
||||
|
||||
function promisify (original) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, ...results) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(...results)
|
||||
}
|
||||
})
|
||||
|
||||
return original(...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function promisifySome (obj, fnNames) {
|
||||
const result = {}
|
||||
for (const fnName of fnNames) {
|
||||
result[fnName] = promisify(obj[fnName])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
exports.afterEach = afterEach
|
||||
exports.beforeEach = beforeEach
|
||||
exports.conditionPromise = conditionPromise
|
||||
exports.emitterEventPromise = emitterEventPromise
|
||||
exports.promisify = promisify
|
||||
exports.promisifySome = promisifySome
|
||||
exports.timeoutPromise = timeoutPromise
|
||||
|
@ -1,5 +1,4 @@
|
||||
const {it, fit, ffit, beforeEach, afterEach, conditionPromise} = require('./async-spec-helpers')
|
||||
const _ = require('underscore-plus')
|
||||
const { conditionPromise } = require('./async-spec-helpers')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const temp = require('temp').track()
|
||||
@ -15,26 +14,26 @@ describe('AtomEnvironment', () => {
|
||||
describe('window sizing methods', () => {
|
||||
describe('::getPosition and ::setPosition', () => {
|
||||
let originalPosition = null
|
||||
beforeEach(() => originalPosition = atom.getPosition())
|
||||
beforeEach(() => (originalPosition = atom.getPosition()))
|
||||
|
||||
afterEach(() => atom.setPosition(originalPosition.x, originalPosition.y))
|
||||
|
||||
it('sets the position of the window, and can retrieve the position just set', () => {
|
||||
atom.setPosition(22, 45)
|
||||
expect(atom.getPosition()).toEqual({x: 22, y: 45})
|
||||
expect(atom.getPosition()).toEqual({ x: 22, y: 45 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('::getSize and ::setSize', () => {
|
||||
let originalSize = null
|
||||
beforeEach(() => originalSize = atom.getSize())
|
||||
beforeEach(() => (originalSize = atom.getSize()))
|
||||
afterEach(() => atom.setSize(originalSize.width, originalSize.height))
|
||||
|
||||
it('sets the size of the window, and can retrieve the size just set', async () => {
|
||||
const newWidth = originalSize.width - 12
|
||||
const newHeight = originalSize.height - 23
|
||||
await atom.setSize(newWidth, newHeight)
|
||||
expect(atom.getSize()).toEqual({width: newWidth, height: newHeight})
|
||||
expect(atom.getSize()).toEqual({ width: newWidth, height: newHeight })
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -67,9 +66,9 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
it('will open the dev tools when an error is triggered', async () => {
|
||||
try {
|
||||
a + 1
|
||||
a + 1 // eslint-disable-line no-undef
|
||||
} catch (e) {
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
window.onerror(e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
|
||||
await devToolsPromise
|
||||
@ -88,10 +87,10 @@ describe('AtomEnvironment', () => {
|
||||
let error = null
|
||||
atom.onWillThrowError(willThrowSpy)
|
||||
try {
|
||||
a + 1
|
||||
a + 1 // eslint-disable-line no-undef
|
||||
} catch (e) {
|
||||
error = e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
window.onerror(e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
|
||||
delete willThrowSpy.mostRecentCall.args[0].preventDefault
|
||||
@ -109,9 +108,9 @@ describe('AtomEnvironment', () => {
|
||||
atom.onWillThrowError(willThrowSpy)
|
||||
|
||||
try {
|
||||
a + 1
|
||||
a + 1 // eslint-disable-line no-undef
|
||||
} catch (e) {
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
window.onerror(e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
|
||||
expect(willThrowSpy).toHaveBeenCalled()
|
||||
@ -122,16 +121,16 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
describe('::onDidThrowError', () => {
|
||||
let didThrowSpy = null
|
||||
beforeEach(() => didThrowSpy = jasmine.createSpy())
|
||||
beforeEach(() => (didThrowSpy = jasmine.createSpy()))
|
||||
|
||||
it('is called when there is an error', () => {
|
||||
let error = null
|
||||
atom.onDidThrowError(didThrowSpy)
|
||||
try {
|
||||
a + 1
|
||||
a + 1 // eslint-disable-line no-undef
|
||||
} catch (e) {
|
||||
error = e
|
||||
window.onerror.call(window, e.toString(), 'abc', 2, 3, e)
|
||||
window.onerror(e.toString(), 'abc', 2, 3, e)
|
||||
}
|
||||
expect(didThrowSpy).toHaveBeenCalledWith({
|
||||
message: error.toString(),
|
||||
@ -165,22 +164,24 @@ describe('AtomEnvironment', () => {
|
||||
describe('if passed a callback function', () => {
|
||||
it("calls the callback with the assertion failure's error object", () => {
|
||||
let error = null
|
||||
atom.assert(false, 'a == b', e => error = e)
|
||||
atom.assert(false, 'a == b', e => (error = e))
|
||||
expect(error).toBe(errors[0])
|
||||
})
|
||||
})
|
||||
|
||||
describe('if passed metadata', () => {
|
||||
it("assigns the metadata on the assertion failure's error object", () => {
|
||||
atom.assert(false, 'a == b', {foo: 'bar'})
|
||||
expect(errors[0].metadata).toEqual({foo: 'bar'})
|
||||
atom.assert(false, 'a == b', { foo: 'bar' })
|
||||
expect(errors[0].metadata).toEqual({ foo: 'bar' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when Atom has been built from source', () => {
|
||||
it('throws an error', () => {
|
||||
atom.isReleasedVersion.andReturn(false)
|
||||
expect(() => atom.assert(false, 'testing')).toThrow('Assertion failed: testing')
|
||||
expect(() => atom.assert(false, 'testing')).toThrow(
|
||||
'Assertion failed: testing'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -195,9 +196,9 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
|
||||
describe('saving and loading', () => {
|
||||
beforeEach(() => atom.enablePersistence = true)
|
||||
beforeEach(() => (atom.enablePersistence = true))
|
||||
|
||||
afterEach(() => atom.enablePersistence = false)
|
||||
afterEach(() => (atom.enablePersistence = false))
|
||||
|
||||
it('selects the state based on the current project paths', async () => {
|
||||
jasmine.useRealClock()
|
||||
@ -210,7 +211,7 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
|
||||
spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings)
|
||||
spyOn(atom, 'serialize').andReturn({stuff: 'cool'})
|
||||
spyOn(atom, 'serialize').andReturn({ stuff: 'cool' })
|
||||
|
||||
atom.project.setPaths([dir1, dir2])
|
||||
|
||||
@ -221,7 +222,7 @@ describe('AtomEnvironment', () => {
|
||||
expect(await atom.loadState()).toBeFalsy()
|
||||
|
||||
loadSettings.initialPaths = [dir2, dir1]
|
||||
expect(await atom.loadState()).toEqual({stuff: 'cool'})
|
||||
expect(await atom.loadState()).toEqual({ stuff: 'cool' })
|
||||
})
|
||||
|
||||
it('saves state when the CPU is idle after a keydown or mousedown event', () => {
|
||||
@ -231,7 +232,9 @@ describe('AtomEnvironment', () => {
|
||||
const idleCallbacks = []
|
||||
atomEnv.initialize({
|
||||
window: {
|
||||
requestIdleCallback (callback) { idleCallbacks.push(callback) },
|
||||
requestIdleCallback (callback) {
|
||||
idleCallbacks.push(callback)
|
||||
},
|
||||
addEventListener () {},
|
||||
removeEventListener () {}
|
||||
},
|
||||
@ -244,16 +247,16 @@ describe('AtomEnvironment', () => {
|
||||
atomEnv.document.dispatchEvent(keydown)
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false })
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true })
|
||||
|
||||
atomEnv.saveState.reset()
|
||||
const mousedown = new MouseEvent('mousedown')
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: false})
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({isUnloading: true})
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false })
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true })
|
||||
|
||||
atomEnv.destroy()
|
||||
})
|
||||
@ -265,7 +268,9 @@ describe('AtomEnvironment', () => {
|
||||
const idleCallbacks = []
|
||||
atomEnv.initialize({
|
||||
window: {
|
||||
requestIdleCallback (callback) { idleCallbacks.push(callback) },
|
||||
requestIdleCallback (callback) {
|
||||
idleCallbacks.push(callback)
|
||||
},
|
||||
addEventListener () {},
|
||||
removeEventListener () {}
|
||||
},
|
||||
@ -278,7 +283,7 @@ describe('AtomEnvironment', () => {
|
||||
atomEnv.document.dispatchEvent(mousedown)
|
||||
expect(atomEnv.saveState).not.toHaveBeenCalled()
|
||||
await atomEnv.prepareToUnloadEditorWindow()
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({isUnloading: true})
|
||||
expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: true })
|
||||
|
||||
advanceClock(atomEnv.saveStateDebounceInterval)
|
||||
idleCallbacks.shift()()
|
||||
@ -294,11 +299,13 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
|
||||
it('serializes the project state with all the options supplied in saveState', async () => {
|
||||
spyOn(atom.project, 'serialize').andReturn({foo: 42})
|
||||
spyOn(atom.project, 'serialize').andReturn({ foo: 42 })
|
||||
|
||||
await atom.saveState({anyOption: 'any option'})
|
||||
await atom.saveState({ anyOption: 'any option' })
|
||||
expect(atom.project.serialize.calls.length).toBe(1)
|
||||
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'})
|
||||
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({
|
||||
anyOption: 'any option'
|
||||
})
|
||||
})
|
||||
|
||||
it('serializes the text editor registry', async () => {
|
||||
@ -309,20 +316,22 @@ describe('AtomEnvironment', () => {
|
||||
const atom2 = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
window: document.createElement('div'),
|
||||
document: Object.assign(
|
||||
document.createElement('div'),
|
||||
{
|
||||
body: document.createElement('div'),
|
||||
head: document.createElement('div')
|
||||
}
|
||||
)
|
||||
document: Object.assign(document.createElement('div'), {
|
||||
body: document.createElement('div'),
|
||||
head: document.createElement('div')
|
||||
})
|
||||
})
|
||||
atom2.initialize({document, window})
|
||||
atom2.initialize({ document, window })
|
||||
|
||||
await atom2.deserialize(atom.serialize())
|
||||
await atom2.packages.activatePackage('language-text')
|
||||
const editor2 = atom2.workspace.getActiveTextEditor()
|
||||
expect(editor2.getBuffer().getLanguageMode().getLanguageId()).toBe('text.plain')
|
||||
expect(
|
||||
editor2
|
||||
.getBuffer()
|
||||
.getLanguageMode()
|
||||
.getLanguageId()
|
||||
).toBe('text.plain')
|
||||
atom2.destroy()
|
||||
})
|
||||
|
||||
@ -335,10 +344,13 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
await atom.deserialize({project: 'should work'})
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open project directory', {
|
||||
description: 'Project directory `/foo` is no longer on disk.'
|
||||
})
|
||||
await atom.deserialize({ project: 'should work' })
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith(
|
||||
'Unable to open project directory',
|
||||
{
|
||||
description: 'Project directory `/foo` is no longer on disk.'
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('accumulates and reports two errors with one notification', async () => {
|
||||
@ -349,10 +361,14 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
await atom.deserialize({project: 'should work'})
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 2 project directories', {
|
||||
description: 'Project directories `/foo` and `/wat` are no longer on disk.'
|
||||
})
|
||||
await atom.deserialize({ project: 'should work' })
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith(
|
||||
'Unable to open 2 project directories',
|
||||
{
|
||||
description:
|
||||
'Project directories `/foo` and `/wat` are no longer on disk.'
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('accumulates and reports three+ errors with one notification', async () => {
|
||||
@ -363,17 +379,23 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
spyOn(atom.notifications, 'addError')
|
||||
|
||||
await atom.deserialize({project: 'should work'})
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith('Unable to open 4 project directories', {
|
||||
description: 'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'
|
||||
})
|
||||
await atom.deserialize({ project: 'should work' })
|
||||
expect(atom.notifications.addError).toHaveBeenCalledWith(
|
||||
'Unable to open 4 project directories',
|
||||
{
|
||||
description:
|
||||
'Project directories `/foo`, `/wat`, `/stuff`, and `/things` are no longer on disk.'
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('openInitialEmptyEditorIfNecessary', () => {
|
||||
describe('when there are no paths set', () => {
|
||||
beforeEach(() => spyOn(atom, 'getLoadSettings').andReturn({initialPaths: []}))
|
||||
beforeEach(() =>
|
||||
spyOn(atom, 'getLoadSettings').andReturn({ initialPaths: [] })
|
||||
)
|
||||
|
||||
it('opens an empty buffer', () => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
@ -396,7 +418,9 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
describe('when the project has a path', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getLoadSettings').andReturn({initialPaths: ['something']})
|
||||
spyOn(atom, 'getLoadSettings').andReturn({
|
||||
initialPaths: ['something']
|
||||
})
|
||||
spyOn(atom.workspace, 'open')
|
||||
})
|
||||
|
||||
@ -410,7 +434,6 @@ describe('AtomEnvironment', () => {
|
||||
describe('adding a project folder', () => {
|
||||
it('does nothing if the user dismisses the file picker', () => {
|
||||
const initialPaths = atom.project.getPaths()
|
||||
const tempDirectory = temp.mkdirSync('a-new-directory')
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback(null))
|
||||
atom.addProjectFolder()
|
||||
expect(atom.project.getPaths()).toEqual(initialPaths)
|
||||
@ -423,9 +446,11 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
|
||||
it('adds the selected folder to the project', async () => {
|
||||
const initialPaths = atom.project.setPaths([])
|
||||
atom.project.setPaths([])
|
||||
const tempDirectory = temp.mkdirSync('a-new-directory')
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback([tempDirectory]))
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback =>
|
||||
callback([tempDirectory])
|
||||
)
|
||||
await atom.addProjectFolder()
|
||||
expect(atom.project.getPaths()).toEqual([tempDirectory])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
@ -437,7 +462,9 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':'))
|
||||
spyOn(atom, 'loadState').andCallFake(async (key) => key === __dirname ? state : null)
|
||||
spyOn(atom, 'loadState').andCallFake(async key =>
|
||||
key === __dirname ? state : null
|
||||
)
|
||||
spyOn(atom, 'attemptRestoreProjectStateForPaths')
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback([__dirname]))
|
||||
atom.project.setPaths([])
|
||||
@ -446,7 +473,10 @@ describe('AtomEnvironment', () => {
|
||||
describe('when there are no project folders', () => {
|
||||
it('attempts to restore the project state', async () => {
|
||||
await atom.addProjectFolder()
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
state,
|
||||
[__dirname]
|
||||
)
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
})
|
||||
@ -481,7 +511,9 @@ describe('AtomEnvironment', () => {
|
||||
fs.writeFileSync(filePath2, 'def')
|
||||
fs.writeFileSync(filePath3, 'ghi')
|
||||
|
||||
const env1 = new AtomEnvironment({applicationDelegate: atom.applicationDelegate})
|
||||
const env1 = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
env1.project.setPaths([projectPath])
|
||||
await env1.workspace.open(filePath1)
|
||||
await env1.workspace.open(filePath2)
|
||||
@ -489,8 +521,14 @@ describe('AtomEnvironment', () => {
|
||||
const env1State = env1.serialize()
|
||||
env1.destroy()
|
||||
|
||||
const env2 = new AtomEnvironment({applicationDelegate: atom.applicationDelegate})
|
||||
await env2.attemptRestoreProjectStateForPaths(env1State, [projectPath], [filePath2])
|
||||
const env2 = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
await env2.attemptRestoreProjectStateForPaths(
|
||||
env1State,
|
||||
[projectPath],
|
||||
[filePath2]
|
||||
)
|
||||
const restoredURIs = env2.workspace.getPaneItems().map(p => p.getURI())
|
||||
expect(restoredURIs).toEqual([filePath1, filePath2, filePath3])
|
||||
env2.destroy()
|
||||
@ -500,12 +538,18 @@ describe('AtomEnvironment', () => {
|
||||
it("doesn't prompt the user to restore state", () => {
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
dock.getActivePane().addItem({
|
||||
getTitle () { return 'title' },
|
||||
getTitle () {
|
||||
return 'title'
|
||||
},
|
||||
element: document.createElement('div')
|
||||
})
|
||||
const state = {}
|
||||
spyOn(atom, 'confirm')
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
atom.attemptRestoreProjectStateForPaths(
|
||||
state,
|
||||
[__dirname],
|
||||
[__filename]
|
||||
)
|
||||
expect(atom.confirm).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@ -527,7 +571,11 @@ describe('AtomEnvironment', () => {
|
||||
spyOn(atom.project, 'addPath')
|
||||
spyOn(atom.workspace, 'open')
|
||||
const state = Symbol()
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
atom.attemptRestoreProjectStateForPaths(
|
||||
state,
|
||||
[__dirname],
|
||||
[__filename]
|
||||
)
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@ -539,7 +587,11 @@ describe('AtomEnvironment', () => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
const state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
atom.attemptRestoreProjectStateForPaths(
|
||||
state,
|
||||
[__dirname],
|
||||
[__filename]
|
||||
)
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
await conditionPromise(() => atom.project.addPath.callCount === 1)
|
||||
|
||||
@ -554,7 +606,11 @@ describe('AtomEnvironment', () => {
|
||||
spyOn(atom, 'open')
|
||||
const state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
atom.attemptRestoreProjectStateForPaths(
|
||||
state,
|
||||
[__dirname],
|
||||
[__filename]
|
||||
)
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
await conditionPromise(() => atom.open.callCount === 1)
|
||||
expect(atom.open).toHaveBeenCalledWith({
|
||||
@ -571,8 +627,16 @@ describe('AtomEnvironment', () => {
|
||||
it('saves the BlobStore so it can be loaded after reload', () => {
|
||||
const configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||
const fakeBlobStore = jasmine.createSpyObj('blob store', ['save'])
|
||||
const atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, enablePersistence: true})
|
||||
atomEnvironment.initialize({configDirPath, blobStore: fakeBlobStore, window, document})
|
||||
const atomEnvironment = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
enablePersistence: true
|
||||
})
|
||||
atomEnvironment.initialize({
|
||||
configDirPath,
|
||||
blobStore: fakeBlobStore,
|
||||
window,
|
||||
document
|
||||
})
|
||||
|
||||
atomEnvironment.unloadEditorWindow()
|
||||
|
||||
@ -584,16 +648,19 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
describe('::destroy()', () => {
|
||||
it('does not throw exceptions when unsubscribing from ipc events (regression)', async () => {
|
||||
const configDirPath = temp.mkdirSync('atom-spec-environment')
|
||||
const fakeDocument = {
|
||||
addEventListener () {},
|
||||
removeEventListener () {},
|
||||
head: document.createElement('head'),
|
||||
body: document.createElement('body')
|
||||
}
|
||||
const atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate})
|
||||
atomEnvironment.initialize({window, document: fakeDocument})
|
||||
spyOn(atomEnvironment.packages, 'loadPackages').andReturn(Promise.resolve())
|
||||
const atomEnvironment = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
atomEnvironment.initialize({ window, document: fakeDocument })
|
||||
spyOn(atomEnvironment.packages, 'loadPackages').andReturn(
|
||||
Promise.resolve()
|
||||
)
|
||||
spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve())
|
||||
spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve())
|
||||
await atomEnvironment.startEditorWindow()
|
||||
@ -606,17 +673,21 @@ describe('AtomEnvironment', () => {
|
||||
let atomEnvironment, envLoaded, spy
|
||||
|
||||
beforeEach(() => {
|
||||
let resolve = null
|
||||
const promise = new Promise((r) => { resolve = r })
|
||||
let resolvePromise = null
|
||||
const promise = new Promise(resolve => {
|
||||
resolvePromise = resolve
|
||||
})
|
||||
envLoaded = () => {
|
||||
resolve()
|
||||
resolvePromise()
|
||||
return promise
|
||||
}
|
||||
atomEnvironment = new AtomEnvironment({
|
||||
applicationDelegate: atom.applicationDelegate,
|
||||
updateProcessEnv () { return promise }
|
||||
updateProcessEnv () {
|
||||
return promise
|
||||
}
|
||||
})
|
||||
atomEnvironment.initialize({window, document})
|
||||
atomEnvironment.initialize({ window, document })
|
||||
spy = jasmine.createSpy()
|
||||
})
|
||||
|
||||
@ -650,23 +721,30 @@ describe('AtomEnvironment', () => {
|
||||
describe('when the opened path exists', () => {
|
||||
it('opens a file', async () => {
|
||||
const pathToOpen = __filename
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
|
||||
it('opens a directory as a project folder', async () => {
|
||||
const pathToOpen = __dirname
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual([])
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual(
|
||||
[]
|
||||
)
|
||||
expect(atom.project.getPaths()).toEqual([pathToOpen])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the opened path does not exist', () => {
|
||||
it('opens it as a new file', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual([pathToOpen])
|
||||
const pathToOpen = path.join(
|
||||
__dirname,
|
||||
'this-path-does-not-exist.txt'
|
||||
)
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual(
|
||||
[pathToOpen]
|
||||
)
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
|
||||
@ -678,9 +756,9 @@ describe('AtomEnvironment', () => {
|
||||
const existingDir = path.join(__dirname, 'fixtures')
|
||||
|
||||
await atom.openLocations([
|
||||
{pathToOpen: nonExistent, mustBeDirectory: true},
|
||||
{pathToOpen: existingFile, mustBeDirectory: true},
|
||||
{pathToOpen: existingDir, mustBeDirectory: true}
|
||||
{ pathToOpen: nonExistent, mustBeDirectory: true },
|
||||
{ pathToOpen: existingFile, mustBeDirectory: true },
|
||||
{ pathToOpen: existingDir, mustBeDirectory: true }
|
||||
])
|
||||
|
||||
expect(atom.workspace.getTextEditors()).toEqual([])
|
||||
@ -688,7 +766,9 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
expect(atom.notifications.addWarning).toHaveBeenCalledWith(
|
||||
'Unable to open project folders',
|
||||
{description: `The directories \`${nonExistent}\` and \`${existingFile}\` do not exist.`}
|
||||
{
|
||||
description: `The directories \`${nonExistent}\` and \`${existingFile}\` do not exist.`
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -697,15 +777,23 @@ describe('AtomEnvironment', () => {
|
||||
let serviceDisposable
|
||||
|
||||
beforeEach(() => {
|
||||
serviceDisposable = atom.packages.serviceHub.provide('atom.directory-provider', '0.1.0', {
|
||||
directoryForURISync (uri) {
|
||||
if (uri.startsWith('remote://')) {
|
||||
return { getPath() { return uri } }
|
||||
} else {
|
||||
return null
|
||||
serviceDisposable = atom.packages.serviceHub.provide(
|
||||
'atom.directory-provider',
|
||||
'0.1.0',
|
||||
{
|
||||
directoryForURISync (uri) {
|
||||
if (uri.startsWith('remote://')) {
|
||||
return {
|
||||
getPath () {
|
||||
return uri
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
waitsFor(() => atom.project.directoryProviders.length > 0)
|
||||
})
|
||||
@ -717,7 +805,7 @@ describe('AtomEnvironment', () => {
|
||||
it("adds it to the project's paths as is", async () => {
|
||||
const pathToOpen = 'remote://server:7644/some/dir/path'
|
||||
spyOn(atom.project, 'addPath')
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)
|
||||
})
|
||||
})
|
||||
@ -729,7 +817,11 @@ describe('AtomEnvironment', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':'))
|
||||
spyOn(atom, 'loadState').andCallFake(function (key) {
|
||||
if (key === __dirname) { return Promise.resolve(state) } else { return Promise.resolve(null) }
|
||||
if (key === __dirname) {
|
||||
return Promise.resolve(state)
|
||||
} else {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
})
|
||||
spyOn(atom, 'attemptRestoreProjectStateForPaths')
|
||||
})
|
||||
@ -737,8 +829,12 @@ describe('AtomEnvironment', () => {
|
||||
describe('when there are no project folders', () => {
|
||||
it('attempts to restore the project state', async () => {
|
||||
const pathToOpen = __dirname
|
||||
await atom.openLocations([{pathToOpen}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], [])
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
state,
|
||||
[pathToOpen],
|
||||
[]
|
||||
)
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
|
||||
@ -755,17 +851,28 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
|
||||
await atom.openLocations([
|
||||
{pathToOpen: existingDir},
|
||||
{pathToOpen: missingDir, mustBeDirectory: true}
|
||||
{ pathToOpen: existingDir },
|
||||
{ pathToOpen: missingDir, mustBeDirectory: true }
|
||||
])
|
||||
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [existingDir], [])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
state,
|
||||
[existingDir],
|
||||
[]
|
||||
)
|
||||
expect(atom.project.getPaths(), [existingDir])
|
||||
})
|
||||
|
||||
it('opens the specified files', async () => {
|
||||
await atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename])
|
||||
await atom.openLocations([
|
||||
{ pathToOpen: __dirname },
|
||||
{ pathToOpen: __filename }
|
||||
])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
state,
|
||||
[__dirname],
|
||||
[__filename]
|
||||
)
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
})
|
||||
@ -775,7 +882,7 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
it('does not attempt to restore the project state, instead adding the project paths', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'fixtures')
|
||||
await atom.openLocations([{pathToOpen, forceAddToWindow: true}])
|
||||
await atom.openLocations([{ pathToOpen, forceAddToWindow: true }])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen])
|
||||
})
|
||||
@ -783,8 +890,10 @@ describe('AtomEnvironment', () => {
|
||||
it('opens the specified files', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'fixtures')
|
||||
const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt')
|
||||
await atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen])
|
||||
await atom.openLocations([{ pathToOpen }, { pathToOpen: fileToOpen }])
|
||||
expect(
|
||||
atom.attemptRestoreProjectStateForPaths
|
||||
).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen])
|
||||
expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen])
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,17 @@
|
||||
/** @babel */
|
||||
|
||||
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
|
||||
import {app} from 'remote'
|
||||
import { app } from 'remote'
|
||||
import atomPaths from '../src/atom-paths'
|
||||
import fs from 'fs-plus'
|
||||
import path from 'path'
|
||||
const temp = require('temp').track()
|
||||
|
||||
describe("AtomPaths", () => {
|
||||
const portableAtomHomePath = path.join(atomPaths.getAppDirectory(), '..', '.atom')
|
||||
describe('AtomPaths', () => {
|
||||
const portableAtomHomePath = path.join(
|
||||
atomPaths.getAppDirectory(),
|
||||
'..',
|
||||
'.atom'
|
||||
)
|
||||
|
||||
afterEach(() => {
|
||||
atomPaths.setAtomHome(app.getPath('home'))
|
||||
@ -18,8 +21,9 @@ describe("AtomPaths", () => {
|
||||
describe('when a portable .atom folder exists', () => {
|
||||
beforeEach(() => {
|
||||
delete process.env.ATOM_HOME
|
||||
if (!fs.existsSync(portableAtomHomePath))
|
||||
if (!fs.existsSync(portableAtomHomePath)) {
|
||||
fs.mkdirSync(portableAtomHomePath)
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -69,6 +73,7 @@ describe("AtomPaths", () => {
|
||||
})
|
||||
|
||||
describe('setUserData', () => {
|
||||
let tempAtomConfigPath = null
|
||||
let tempAtomHomePath = null
|
||||
let electronUserDataPath = null
|
||||
let defaultElectronUserDataPath = null
|
||||
|
@ -1,5 +1,5 @@
|
||||
const AutoUpdateManager = require('../src/auto-update-manager')
|
||||
const {remote} = require('electron')
|
||||
const { remote } = require('electron')
|
||||
const electronAutoUpdater = remote.require('electron').autoUpdater
|
||||
|
||||
describe('AutoUpdateManager (renderer)', () => {
|
||||
@ -8,7 +8,9 @@ describe('AutoUpdateManager (renderer)', () => {
|
||||
let autoUpdateManager
|
||||
|
||||
beforeEach(() => {
|
||||
autoUpdateManager = new AutoUpdateManager({applicationDelegate: atom.applicationDelegate})
|
||||
autoUpdateManager = new AutoUpdateManager({
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
autoUpdateManager.initialize()
|
||||
})
|
||||
|
||||
@ -69,14 +71,16 @@ describe('AutoUpdateManager (renderer)', () => {
|
||||
autoUpdateManager.onUpdateError(spy)
|
||||
electronAutoUpdater.emit('error', {}, 'an error message')
|
||||
waitsFor(() => spy.callCount === 1)
|
||||
runs(() => expect(autoUpdateManager.getErrorMessage()).toBe('an error message'))
|
||||
runs(() =>
|
||||
expect(autoUpdateManager.getErrorMessage()).toBe('an error message')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('::platformSupportsUpdates', () => {
|
||||
let state, releaseChannel
|
||||
it('returns true on macOS and Windows when in stable', () => {
|
||||
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
|
||||
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
|
||||
spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel)
|
||||
|
||||
state = 'idle'
|
||||
|
@ -1,7 +1,6 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const temp = require('temp').track()
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers');
|
||||
const CommandInstaller = require('../src/command-installer')
|
||||
|
||||
describe('CommandInstaller on #darwin', () => {
|
||||
@ -12,14 +11,25 @@ describe('CommandInstaller on #darwin', () => {
|
||||
|
||||
resourcesPath = temp.mkdirSync('atom-app')
|
||||
atomBinPath = path.join(resourcesPath, 'app', 'atom.sh')
|
||||
apmBinPath = path.join(resourcesPath, 'app', 'apm', 'node_modules', '.bin', 'apm')
|
||||
apmBinPath = path.join(
|
||||
resourcesPath,
|
||||
'app',
|
||||
'apm',
|
||||
'node_modules',
|
||||
'.bin',
|
||||
'apm'
|
||||
)
|
||||
fs.writeFileSync(atomBinPath, '')
|
||||
fs.writeFileSync(apmBinPath, '')
|
||||
fs.chmodSync(atomBinPath, '755')
|
||||
fs.chmodSync(apmBinPath, '755')
|
||||
|
||||
spyOn(CommandInstaller.prototype, 'getResourcesDirectory').andReturn(resourcesPath)
|
||||
spyOn(CommandInstaller.prototype, 'getInstallDirectory').andReturn(installationPath)
|
||||
spyOn(CommandInstaller.prototype, 'getResourcesDirectory').andReturn(
|
||||
resourcesPath
|
||||
)
|
||||
spyOn(CommandInstaller.prototype, 'getInstallDirectory').andReturn(
|
||||
installationPath
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -32,7 +42,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
const appDelegate = jasmine.createSpyObj('appDelegate', ['confirm'])
|
||||
installer = new CommandInstaller(appDelegate)
|
||||
installer.initialize('2.0.2')
|
||||
spyOn(installer, 'installAtomCommand').andCallFake((__, callback) => callback(new Error('an error')))
|
||||
spyOn(installer, 'installAtomCommand').andCallFake((__, callback) =>
|
||||
callback(new Error('an error'))
|
||||
)
|
||||
|
||||
installer.installShellCommandsInteractively()
|
||||
|
||||
@ -43,7 +55,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
|
||||
appDelegate.confirm.reset()
|
||||
installer.installAtomCommand.andCallFake((__, callback) => callback())
|
||||
spyOn(installer, 'installApmCommand').andCallFake((__, callback) => callback(new Error('another error')))
|
||||
spyOn(installer, 'installApmCommand').andCallFake((__, callback) =>
|
||||
callback(new Error('another error'))
|
||||
)
|
||||
|
||||
installer.installShellCommandsInteractively()
|
||||
|
||||
@ -57,8 +71,12 @@ describe('CommandInstaller on #darwin', () => {
|
||||
const appDelegate = jasmine.createSpyObj('appDelegate', ['confirm'])
|
||||
installer = new CommandInstaller(appDelegate)
|
||||
installer.initialize('2.0.2')
|
||||
spyOn(installer, 'installAtomCommand').andCallFake((__, callback) => callback(undefined, 'atom'))
|
||||
spyOn(installer, 'installApmCommand').andCallFake((__, callback) => callback(undefined, 'apm'))
|
||||
spyOn(installer, 'installAtomCommand').andCallFake((__, callback) =>
|
||||
callback(undefined, 'atom')
|
||||
)
|
||||
spyOn(installer, 'installApmCommand').andCallFake((__, callback) =>
|
||||
callback(undefined, 'apm')
|
||||
)
|
||||
|
||||
installer.installShellCommandsInteractively()
|
||||
|
||||
@ -81,9 +99,13 @@ describe('CommandInstaller on #darwin', () => {
|
||||
waitsFor(done => {
|
||||
installer.installAtomCommand(false, error => {
|
||||
expect(error).toBeNull()
|
||||
expect(fs.realpathSync(installedAtomPath)).toBe(fs.realpathSync(atomBinPath))
|
||||
expect(fs.realpathSync(installedAtomPath)).toBe(
|
||||
fs.realpathSync(atomBinPath)
|
||||
)
|
||||
expect(fs.isExecutableSync(installedAtomPath)).toBe(true)
|
||||
expect(fs.isFileSync(path.join(installationPath, 'atom-beta'))).toBe(false)
|
||||
expect(fs.isFileSync(path.join(installationPath, 'atom-beta'))).toBe(
|
||||
false
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
@ -96,9 +118,13 @@ describe('CommandInstaller on #darwin', () => {
|
||||
waitsFor(done => {
|
||||
installer.installApmCommand(false, error => {
|
||||
expect(error).toBeNull()
|
||||
expect(fs.realpathSync(installedApmPath)).toBe(fs.realpathSync(apmBinPath))
|
||||
expect(fs.realpathSync(installedApmPath)).toBe(
|
||||
fs.realpathSync(apmBinPath)
|
||||
)
|
||||
expect(fs.isExecutableSync(installedApmPath)).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(installationPath, 'apm-beta'))).toBe(false)
|
||||
expect(fs.isFileSync(path.join(installationPath, 'apm-beta'))).toBe(
|
||||
false
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
@ -118,7 +144,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
waitsFor(done => {
|
||||
installer.installAtomCommand(false, error => {
|
||||
expect(error).toBeNull()
|
||||
expect(fs.realpathSync(installedAtomPath)).toBe(fs.realpathSync(atomBinPath))
|
||||
expect(fs.realpathSync(installedAtomPath)).toBe(
|
||||
fs.realpathSync(atomBinPath)
|
||||
)
|
||||
expect(fs.isExecutableSync(installedAtomPath)).toBe(true)
|
||||
expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe(false)
|
||||
done()
|
||||
@ -133,7 +161,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
waitsFor(done => {
|
||||
installer.installApmCommand(false, error => {
|
||||
expect(error).toBeNull()
|
||||
expect(fs.realpathSync(installedApmPath)).toBe(fs.realpathSync(apmBinPath))
|
||||
expect(fs.realpathSync(installedApmPath)).toBe(
|
||||
fs.realpathSync(apmBinPath)
|
||||
)
|
||||
expect(fs.isExecutableSync(installedApmPath)).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(installationPath, 'apm'))).toBe(false)
|
||||
done()
|
||||
@ -155,7 +185,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
waitsFor(done => {
|
||||
installer.installAtomCommand(false, error => {
|
||||
expect(error).toBeNull()
|
||||
expect(fs.realpathSync(installedAtomPath)).toBe(fs.realpathSync(atomBinPath))
|
||||
expect(fs.realpathSync(installedAtomPath)).toBe(
|
||||
fs.realpathSync(atomBinPath)
|
||||
)
|
||||
expect(fs.isExecutableSync(installedAtomPath)).toBe(true)
|
||||
expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe(false)
|
||||
done()
|
||||
@ -170,9 +202,13 @@ describe('CommandInstaller on #darwin', () => {
|
||||
waitsFor(done => {
|
||||
installer.installApmCommand(false, error => {
|
||||
expect(error).toBeNull()
|
||||
expect(fs.realpathSync(installedApmPath)).toBe(fs.realpathSync(apmBinPath))
|
||||
expect(fs.realpathSync(installedApmPath)).toBe(
|
||||
fs.realpathSync(apmBinPath)
|
||||
)
|
||||
expect(fs.isExecutableSync(installedApmPath)).toBeTruthy()
|
||||
expect(fs.isFileSync(path.join(installationPath, 'nightly'))).toBe(false)
|
||||
expect(fs.isFileSync(path.join(installationPath, 'nightly'))).toBe(
|
||||
false
|
||||
)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
@ -1,289 +1,306 @@
|
||||
const CommandRegistry = require('../src/command-registry');
|
||||
const _ = require('underscore-plus');
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers');
|
||||
const CommandRegistry = require('../src/command-registry')
|
||||
const _ = require('underscore-plus')
|
||||
|
||||
describe("CommandRegistry", () => {
|
||||
let registry, parent, child, grandchild;
|
||||
describe('CommandRegistry', () => {
|
||||
let registry, parent, child, grandchild
|
||||
|
||||
beforeEach(() => {
|
||||
parent = document.createElement("div");
|
||||
child = document.createElement("div");
|
||||
grandchild = document.createElement("div");
|
||||
parent.classList.add('parent');
|
||||
child.classList.add('child');
|
||||
grandchild.classList.add('grandchild');
|
||||
child.appendChild(grandchild);
|
||||
parent.appendChild(child);
|
||||
document.querySelector('#jasmine-content').appendChild(parent);
|
||||
parent = document.createElement('div')
|
||||
child = document.createElement('div')
|
||||
grandchild = document.createElement('div')
|
||||
parent.classList.add('parent')
|
||||
child.classList.add('child')
|
||||
grandchild.classList.add('grandchild')
|
||||
child.appendChild(grandchild)
|
||||
parent.appendChild(child)
|
||||
document.querySelector('#jasmine-content').appendChild(parent)
|
||||
|
||||
registry = new CommandRegistry;
|
||||
registry.attach(parent);
|
||||
});
|
||||
registry = new CommandRegistry()
|
||||
registry.attach(parent)
|
||||
})
|
||||
|
||||
afterEach(() => registry.destroy());
|
||||
afterEach(() => registry.destroy())
|
||||
|
||||
describe("when a command event is dispatched on an element", () => {
|
||||
it("invokes callbacks with selectors matching the target", () => {
|
||||
let called = false;
|
||||
describe('when a command event is dispatched on an element', () => {
|
||||
it('invokes callbacks with selectors matching the target', () => {
|
||||
let called = false
|
||||
registry.add('.grandchild', 'command', function (event) {
|
||||
expect(this).toBe(grandchild);
|
||||
expect(event.type).toBe('command');
|
||||
expect(event.eventPhase).toBe(Event.BUBBLING_PHASE);
|
||||
expect(event.target).toBe(grandchild);
|
||||
expect(event.currentTarget).toBe(grandchild);
|
||||
called = true;
|
||||
});
|
||||
expect(this).toBe(grandchild)
|
||||
expect(event.type).toBe('command')
|
||||
expect(event.eventPhase).toBe(Event.BUBBLING_PHASE)
|
||||
expect(event.target).toBe(grandchild)
|
||||
expect(event.currentTarget).toBe(grandchild)
|
||||
called = true
|
||||
})
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
|
||||
it("invokes callbacks with selectors matching ancestors of the target", () => {
|
||||
const calls = [];
|
||||
it('invokes callbacks with selectors matching ancestors of the target', () => {
|
||||
const calls = []
|
||||
|
||||
registry.add('.child', 'command', function (event) {
|
||||
expect(this).toBe(child);
|
||||
expect(event.target).toBe(grandchild);
|
||||
expect(event.currentTarget).toBe(child);
|
||||
calls.push('child');
|
||||
});
|
||||
expect(this).toBe(child)
|
||||
expect(event.target).toBe(grandchild)
|
||||
expect(event.currentTarget).toBe(child)
|
||||
calls.push('child')
|
||||
})
|
||||
|
||||
registry.add('.parent', 'command', function (event) {
|
||||
expect(this).toBe(parent);
|
||||
expect(event.target).toBe(grandchild);
|
||||
expect(event.currentTarget).toBe(parent);
|
||||
calls.push('parent');
|
||||
});
|
||||
registry.add('.parent', 'command', function (event) {
|
||||
expect(this).toBe(parent)
|
||||
expect(event.target).toBe(grandchild)
|
||||
expect(event.currentTarget).toBe(parent)
|
||||
calls.push('parent')
|
||||
})
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(calls).toEqual(['child', 'parent']);
|
||||
});
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(calls).toEqual(['child', 'parent'])
|
||||
})
|
||||
|
||||
it("invokes inline listeners prior to listeners applied via selectors", () => {
|
||||
const calls = [];
|
||||
registry.add('.grandchild', 'command', () => calls.push('grandchild'));
|
||||
registry.add(child, 'command', () => calls.push('child-inline'));
|
||||
registry.add('.child', 'command', () => calls.push('child'));
|
||||
registry.add('.parent', 'command', () => calls.push('parent'));
|
||||
it('invokes inline listeners prior to listeners applied via selectors', () => {
|
||||
const calls = []
|
||||
registry.add('.grandchild', 'command', () => calls.push('grandchild'))
|
||||
registry.add(child, 'command', () => calls.push('child-inline'))
|
||||
registry.add('.child', 'command', () => calls.push('child'))
|
||||
registry.add('.parent', 'command', () => calls.push('parent'))
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(calls).toEqual(['grandchild', 'child-inline', 'child', 'parent']);
|
||||
});
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(calls).toEqual(['grandchild', 'child-inline', 'child', 'parent'])
|
||||
})
|
||||
|
||||
it("orders multiple matching listeners for an element by selector specificity", () => {
|
||||
child.classList.add('foo', 'bar');
|
||||
const calls = [];
|
||||
it('orders multiple matching listeners for an element by selector specificity', () => {
|
||||
child.classList.add('foo', 'bar')
|
||||
const calls = []
|
||||
|
||||
registry.add('.foo.bar', 'command', () => calls.push('.foo.bar'));
|
||||
registry.add('.foo', 'command', () => calls.push('.foo'));
|
||||
registry.add('.bar', 'command', () => calls.push('.bar')); // specificity ties favor commands added later, like CSS
|
||||
registry.add('.foo.bar', 'command', () => calls.push('.foo.bar'))
|
||||
registry.add('.foo', 'command', () => calls.push('.foo'))
|
||||
registry.add('.bar', 'command', () => calls.push('.bar')) // specificity ties favor commands added later, like CSS
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(calls).toEqual(['.foo.bar', '.bar', '.foo']);
|
||||
});
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(calls).toEqual(['.foo.bar', '.bar', '.foo'])
|
||||
})
|
||||
|
||||
it("orders inline listeners by reverse registration order", () => {
|
||||
const calls = [];
|
||||
registry.add(child, 'command', () => calls.push('child1'));
|
||||
registry.add(child, 'command', () => calls.push('child2'));
|
||||
child.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(calls).toEqual(['child2', 'child1']);
|
||||
});
|
||||
it('orders inline listeners by reverse registration order', () => {
|
||||
const calls = []
|
||||
registry.add(child, 'command', () => calls.push('child1'))
|
||||
registry.add(child, 'command', () => calls.push('child2'))
|
||||
child.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(calls).toEqual(['child2', 'child1'])
|
||||
})
|
||||
|
||||
it("stops bubbling through ancestors when .stopPropagation() is called on the event", () => {
|
||||
const calls = [];
|
||||
it('stops bubbling through ancestors when .stopPropagation() is called on the event', () => {
|
||||
const calls = []
|
||||
|
||||
registry.add('.parent', 'command', () => calls.push('parent'));
|
||||
registry.add('.child', 'command', () => calls.push('child-2'));
|
||||
registry.add('.child', 'command', (event) => {
|
||||
calls.push('child-1');
|
||||
event.stopPropagation();
|
||||
});
|
||||
registry.add('.parent', 'command', () => calls.push('parent'))
|
||||
registry.add('.child', 'command', () => calls.push('child-2'))
|
||||
registry.add('.child', 'command', event => {
|
||||
calls.push('child-1')
|
||||
event.stopPropagation()
|
||||
})
|
||||
|
||||
const dispatchedEvent = new CustomEvent('command', {bubbles: true});
|
||||
spyOn(dispatchedEvent, 'stopPropagation');
|
||||
grandchild.dispatchEvent(dispatchedEvent);
|
||||
expect(calls).toEqual(['child-1', 'child-2']);
|
||||
expect(dispatchedEvent.stopPropagation).toHaveBeenCalled();
|
||||
});
|
||||
const dispatchedEvent = new CustomEvent('command', { bubbles: true })
|
||||
spyOn(dispatchedEvent, 'stopPropagation')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(calls).toEqual(['child-1', 'child-2'])
|
||||
expect(dispatchedEvent.stopPropagation).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("stops invoking callbacks when .stopImmediatePropagation() is called on the event", () => {
|
||||
const calls = [];
|
||||
it('stops invoking callbacks when .stopImmediatePropagation() is called on the event', () => {
|
||||
const calls = []
|
||||
|
||||
registry.add('.parent', 'command', () => calls.push('parent'));
|
||||
registry.add('.child', 'command', () => calls.push('child-2'));
|
||||
registry.add('.child', 'command', (event) => {
|
||||
calls.push('child-1');
|
||||
event.stopImmediatePropagation();
|
||||
});
|
||||
registry.add('.parent', 'command', () => calls.push('parent'))
|
||||
registry.add('.child', 'command', () => calls.push('child-2'))
|
||||
registry.add('.child', 'command', event => {
|
||||
calls.push('child-1')
|
||||
event.stopImmediatePropagation()
|
||||
})
|
||||
|
||||
const dispatchedEvent = new CustomEvent('command', {bubbles: true});
|
||||
spyOn(dispatchedEvent, 'stopImmediatePropagation');
|
||||
grandchild.dispatchEvent(dispatchedEvent);
|
||||
expect(calls).toEqual(['child-1']);
|
||||
expect(dispatchedEvent.stopImmediatePropagation).toHaveBeenCalled();
|
||||
});
|
||||
const dispatchedEvent = new CustomEvent('command', { bubbles: true })
|
||||
spyOn(dispatchedEvent, 'stopImmediatePropagation')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(calls).toEqual(['child-1'])
|
||||
expect(dispatchedEvent.stopImmediatePropagation).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("forwards .preventDefault() calls from the synthetic event to the original", () => {
|
||||
registry.add('.child', 'command', event => event.preventDefault());
|
||||
it('forwards .preventDefault() calls from the synthetic event to the original', () => {
|
||||
registry.add('.child', 'command', event => event.preventDefault())
|
||||
|
||||
const dispatchedEvent = new CustomEvent('command', {bubbles: true});
|
||||
spyOn(dispatchedEvent, 'preventDefault');
|
||||
grandchild.dispatchEvent(dispatchedEvent);
|
||||
expect(dispatchedEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
const dispatchedEvent = new CustomEvent('command', { bubbles: true })
|
||||
spyOn(dispatchedEvent, 'preventDefault')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(dispatchedEvent.preventDefault).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("forwards .abortKeyBinding() calls from the synthetic event to the original", () => {
|
||||
registry.add('.child', 'command', event => event.abortKeyBinding());
|
||||
it('forwards .abortKeyBinding() calls from the synthetic event to the original', () => {
|
||||
registry.add('.child', 'command', event => event.abortKeyBinding())
|
||||
|
||||
const dispatchedEvent = new CustomEvent('command', {bubbles: true});
|
||||
dispatchedEvent.abortKeyBinding = jasmine.createSpy('abortKeyBinding');
|
||||
grandchild.dispatchEvent(dispatchedEvent);
|
||||
expect(dispatchedEvent.abortKeyBinding).toHaveBeenCalled();
|
||||
});
|
||||
const dispatchedEvent = new CustomEvent('command', { bubbles: true })
|
||||
dispatchedEvent.abortKeyBinding = jasmine.createSpy('abortKeyBinding')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(dispatchedEvent.abortKeyBinding).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("copies non-standard properties from the original event to the synthetic event", () => {
|
||||
let syntheticEvent = null;
|
||||
registry.add('.child', 'command', event => syntheticEvent = event);
|
||||
it('copies non-standard properties from the original event to the synthetic event', () => {
|
||||
let syntheticEvent = null
|
||||
registry.add('.child', 'command', event => (syntheticEvent = event))
|
||||
|
||||
const dispatchedEvent = new CustomEvent('command', {bubbles: true});
|
||||
dispatchedEvent.nonStandardProperty = 'testing';
|
||||
grandchild.dispatchEvent(dispatchedEvent);
|
||||
expect(syntheticEvent.nonStandardProperty).toBe('testing');
|
||||
});
|
||||
const dispatchedEvent = new CustomEvent('command', { bubbles: true })
|
||||
dispatchedEvent.nonStandardProperty = 'testing'
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(syntheticEvent.nonStandardProperty).toBe('testing')
|
||||
})
|
||||
|
||||
it("allows listeners to be removed via a disposable returned by ::add", () => {
|
||||
let calls = [];
|
||||
it('allows listeners to be removed via a disposable returned by ::add', () => {
|
||||
let calls = []
|
||||
|
||||
const disposable1 = registry.add('.parent', 'command', () => calls.push('parent'));
|
||||
const disposable2 = registry.add('.child', 'command', () => calls.push('child'));
|
||||
const disposable1 = registry.add('.parent', 'command', () =>
|
||||
calls.push('parent')
|
||||
)
|
||||
const disposable2 = registry.add('.child', 'command', () =>
|
||||
calls.push('child')
|
||||
)
|
||||
|
||||
disposable1.dispose();
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(calls).toEqual(['child']);
|
||||
disposable1.dispose()
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(calls).toEqual(['child'])
|
||||
|
||||
calls = [];
|
||||
disposable2.dispose();
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
expect(calls).toEqual([]);
|
||||
});
|
||||
calls = []
|
||||
disposable2.dispose()
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
expect(calls).toEqual([])
|
||||
})
|
||||
|
||||
it("allows multiple commands to be registered under one selector when called with an object", () => {
|
||||
let calls = [];
|
||||
it('allows multiple commands to be registered under one selector when called with an object', () => {
|
||||
let calls = []
|
||||
|
||||
const disposable = registry.add('.child', {
|
||||
'command-1'() {
|
||||
calls.push('command-1');
|
||||
'command-1' () {
|
||||
calls.push('command-1')
|
||||
},
|
||||
'command-2'() {
|
||||
calls.push('command-2');
|
||||
'command-2' () {
|
||||
calls.push('command-2')
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', {bubbles: true}));
|
||||
grandchild.dispatchEvent(new CustomEvent('command-2', {bubbles: true}));
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true }))
|
||||
grandchild.dispatchEvent(new CustomEvent('command-2', { bubbles: true }))
|
||||
|
||||
expect(calls).toEqual(['command-1', 'command-2']);
|
||||
expect(calls).toEqual(['command-1', 'command-2'])
|
||||
|
||||
calls = [];
|
||||
disposable.dispose();
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', {bubbles: true}));
|
||||
grandchild.dispatchEvent(new CustomEvent('command-2', {bubbles: true}));
|
||||
expect(calls).toEqual([]);
|
||||
});
|
||||
calls = []
|
||||
disposable.dispose()
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true }))
|
||||
grandchild.dispatchEvent(new CustomEvent('command-2', { bubbles: true }))
|
||||
expect(calls).toEqual([])
|
||||
})
|
||||
|
||||
it("invokes callbacks registered with ::onWillDispatch and ::onDidDispatch", () => {
|
||||
const sequence = [];
|
||||
it('invokes callbacks registered with ::onWillDispatch and ::onDidDispatch', () => {
|
||||
const sequence = []
|
||||
|
||||
registry.onDidDispatch(event => sequence.push(['onDidDispatch', event]));
|
||||
registry.onDidDispatch(event => sequence.push(['onDidDispatch', event]))
|
||||
|
||||
registry.add('.grandchild', 'command', event => sequence.push(['listener', event]));
|
||||
registry.add('.grandchild', 'command', event =>
|
||||
sequence.push(['listener', event])
|
||||
)
|
||||
|
||||
registry.onWillDispatch(event => sequence.push(['onWillDispatch', event]));
|
||||
registry.onWillDispatch(event => sequence.push(['onWillDispatch', event]))
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', {bubbles: true}));
|
||||
grandchild.dispatchEvent(new CustomEvent('command', { bubbles: true }))
|
||||
|
||||
expect(sequence[0][0]).toBe('onWillDispatch');
|
||||
expect(sequence[1][0]).toBe('listener');
|
||||
expect(sequence[2][0]).toBe('onDidDispatch');
|
||||
expect(sequence[0][0]).toBe('onWillDispatch')
|
||||
expect(sequence[1][0]).toBe('listener')
|
||||
expect(sequence[2][0]).toBe('onDidDispatch')
|
||||
|
||||
expect(sequence[0][1] === sequence[1][1] && sequence[1][1] === sequence[2][1]).toBe(true);
|
||||
expect(sequence[0][1].constructor).toBe(CustomEvent);
|
||||
expect(sequence[0][1].target).toBe(grandchild);
|
||||
});
|
||||
});
|
||||
expect(
|
||||
sequence[0][1] === sequence[1][1] && sequence[1][1] === sequence[2][1]
|
||||
).toBe(true)
|
||||
expect(sequence[0][1].constructor).toBe(CustomEvent)
|
||||
expect(sequence[0][1].target).toBe(grandchild)
|
||||
})
|
||||
})
|
||||
|
||||
describe("::add(selector, commandName, callback)", () => {
|
||||
it("throws an error when called with an invalid selector", () => {
|
||||
const badSelector = '<>';
|
||||
let addError = null;
|
||||
describe('::add(selector, commandName, callback)', () => {
|
||||
it('throws an error when called with an invalid selector', () => {
|
||||
const badSelector = '<>'
|
||||
let addError = null
|
||||
try {
|
||||
registry.add(badSelector, 'foo:bar', () => {});
|
||||
registry.add(badSelector, 'foo:bar', () => {})
|
||||
} catch (error) {
|
||||
addError = error;
|
||||
addError = error
|
||||
}
|
||||
expect(addError.message).toContain(badSelector);
|
||||
});
|
||||
expect(addError.message).toContain(badSelector)
|
||||
})
|
||||
|
||||
it("throws an error when called with a null callback and selector target", () => {
|
||||
const badCallback = null;
|
||||
it('throws an error when called with a null callback and selector target', () => {
|
||||
const badCallback = null
|
||||
|
||||
expect(() => {
|
||||
registry.add('.selector', 'foo:bar', badCallback);
|
||||
}).toThrow(new Error('Cannot register a command with a null listener.'));
|
||||
});
|
||||
registry.add('.selector', 'foo:bar', badCallback)
|
||||
}).toThrow(new Error('Cannot register a command with a null listener.'))
|
||||
})
|
||||
|
||||
it("throws an error when called with a null callback and object target", () => {
|
||||
const badCallback = null;
|
||||
it('throws an error when called with a null callback and object target', () => {
|
||||
const badCallback = null
|
||||
|
||||
expect(() => {
|
||||
registry.add(document.body, 'foo:bar', badCallback);
|
||||
}).toThrow(new Error('Cannot register a command with a null listener.'));
|
||||
});
|
||||
registry.add(document.body, 'foo:bar', badCallback)
|
||||
}).toThrow(new Error('Cannot register a command with a null listener.'))
|
||||
})
|
||||
|
||||
it("throws an error when called with an object listener without a didDispatch method", () => {
|
||||
it('throws an error when called with an object listener without a didDispatch method', () => {
|
||||
const badListener = {
|
||||
title: 'a listener without a didDispatch callback',
|
||||
description: 'this should throw an error'
|
||||
};
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
registry.add(document.body, 'foo:bar', badListener);
|
||||
}).toThrow(new Error('Listener must be a callback function or an object with a didDispatch method.'));
|
||||
});
|
||||
});
|
||||
registry.add(document.body, 'foo:bar', badListener)
|
||||
}).toThrow(
|
||||
new Error(
|
||||
'Listener must be a callback function or an object with a didDispatch method.'
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("::findCommands({target})", () => {
|
||||
it("returns command descriptors that can be invoked on the target or its ancestors", () => {
|
||||
registry.add('.parent', 'namespace:command-1', () => {});
|
||||
registry.add('.child', 'namespace:command-2', () => {});
|
||||
registry.add('.grandchild', 'namespace:command-3', () => {});
|
||||
registry.add('.grandchild.no-match', 'namespace:command-4', () => {});
|
||||
describe('::findCommands({target})', () => {
|
||||
it('returns command descriptors that can be invoked on the target or its ancestors', () => {
|
||||
registry.add('.parent', 'namespace:command-1', () => {})
|
||||
registry.add('.child', 'namespace:command-2', () => {})
|
||||
registry.add('.grandchild', 'namespace:command-3', () => {})
|
||||
registry.add('.grandchild.no-match', 'namespace:command-4', () => {})
|
||||
|
||||
registry.add(grandchild, 'namespace:inline-command-1', () => {});
|
||||
registry.add(child, 'namespace:inline-command-2', () => {});
|
||||
registry.add(grandchild, 'namespace:inline-command-1', () => {})
|
||||
registry.add(child, 'namespace:inline-command-2', () => {})
|
||||
|
||||
const commands = registry.findCommands({target: grandchild});
|
||||
const nonJqueryCommands = _.reject(commands, cmd => cmd.jQuery);
|
||||
const commands = registry.findCommands({ target: grandchild })
|
||||
const nonJqueryCommands = _.reject(commands, cmd => cmd.jQuery)
|
||||
expect(nonJqueryCommands).toEqual([
|
||||
{name: 'namespace:inline-command-1', displayName: 'Namespace: Inline Command 1'},
|
||||
{name: 'namespace:command-3', displayName: 'Namespace: Command 3'},
|
||||
{name: 'namespace:inline-command-2', displayName: 'Namespace: Inline Command 2'},
|
||||
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'},
|
||||
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
|
||||
]);
|
||||
});
|
||||
{
|
||||
name: 'namespace:inline-command-1',
|
||||
displayName: 'Namespace: Inline Command 1'
|
||||
},
|
||||
{ name: 'namespace:command-3', displayName: 'Namespace: Command 3' },
|
||||
{
|
||||
name: 'namespace:inline-command-2',
|
||||
displayName: 'Namespace: Inline Command 2'
|
||||
},
|
||||
{ name: 'namespace:command-2', displayName: 'Namespace: Command 2' },
|
||||
{ name: 'namespace:command-1', displayName: 'Namespace: Command 1' }
|
||||
])
|
||||
})
|
||||
|
||||
it("returns command descriptors with arbitrary metadata if set in a listener object", () => {
|
||||
registry.add('.grandchild', 'namespace:command-1', () => {});
|
||||
it('returns command descriptors with arbitrary metadata if set in a listener object', () => {
|
||||
registry.add('.grandchild', 'namespace:command-1', () => {})
|
||||
registry.add('.grandchild', 'namespace:command-2', {
|
||||
displayName: 'Custom Command 2',
|
||||
metadata: {
|
||||
some: 'other',
|
||||
object: 'data'
|
||||
},
|
||||
didDispatch() {}
|
||||
});
|
||||
didDispatch () {}
|
||||
})
|
||||
registry.add('.grandchild', 'namespace:command-3', {
|
||||
name: 'some:other:incorrect:commandname',
|
||||
displayName: 'Custom Command 3',
|
||||
@ -291,10 +308,10 @@ describe("CommandRegistry", () => {
|
||||
some: 'other',
|
||||
object: 'data'
|
||||
},
|
||||
didDispatch() {}
|
||||
});
|
||||
didDispatch () {}
|
||||
})
|
||||
|
||||
const commands = registry.findCommands({target: grandchild});
|
||||
const commands = registry.findCommands({ target: grandchild })
|
||||
expect(commands).toEqual([
|
||||
{
|
||||
displayName: 'Namespace: Command 1',
|
||||
@ -303,143 +320,163 @@ describe("CommandRegistry", () => {
|
||||
{
|
||||
displayName: 'Custom Command 2',
|
||||
metadata: {
|
||||
some : 'other',
|
||||
object : 'data'
|
||||
some: 'other',
|
||||
object: 'data'
|
||||
},
|
||||
name: 'namespace:command-2'
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Command 3',
|
||||
metadata: {
|
||||
some : 'other',
|
||||
object : 'data'
|
||||
some: 'other',
|
||||
object: 'data'
|
||||
},
|
||||
name: 'namespace:command-3'
|
||||
}
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it("returns command descriptors with arbitrary metadata if set on a listener function", () => {
|
||||
it('returns command descriptors with arbitrary metadata if set on a listener function', () => {
|
||||
function listener () {}
|
||||
listener.displayName = 'Custom Command 2'
|
||||
listener.metadata = {
|
||||
some: 'other',
|
||||
object: 'data'
|
||||
};
|
||||
}
|
||||
|
||||
registry.add('.grandchild', 'namespace:command-2', listener);
|
||||
const commands = registry.findCommands({target: grandchild});
|
||||
registry.add('.grandchild', 'namespace:command-2', listener)
|
||||
const commands = registry.findCommands({ target: grandchild })
|
||||
expect(commands).toEqual([
|
||||
{
|
||||
displayName : 'Custom Command 2',
|
||||
displayName: 'Custom Command 2',
|
||||
metadata: {
|
||||
some: 'other',
|
||||
object: 'data'
|
||||
},
|
||||
name: 'namespace:command-2'
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("::dispatch(target, commandName)", () => {
|
||||
it("simulates invocation of the given command ", () => {
|
||||
let called = false;
|
||||
registry.add('.grandchild', 'command', function (event) {
|
||||
expect(this).toBe(grandchild);
|
||||
expect(event.type).toBe('command');
|
||||
expect(event.eventPhase).toBe(Event.BUBBLING_PHASE);
|
||||
expect(event.target).toBe(grandchild);
|
||||
expect(event.currentTarget).toBe(grandchild);
|
||||
called = true;
|
||||
});
|
||||
|
||||
registry.dispatch(grandchild, 'command');
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it("returns a promise if any listeners matched the command", () => {
|
||||
registry.add('.grandchild', 'command', () => {});
|
||||
|
||||
expect(registry.dispatch(grandchild, 'command').constructor.name).toBe("Promise");
|
||||
expect(registry.dispatch(grandchild, 'bogus')).toBe(null);
|
||||
expect(registry.dispatch(parent, 'command')).toBe(null);
|
||||
});
|
||||
|
||||
it("returns a promise that resolves when the listeners resolve", async () => {
|
||||
jasmine.useRealClock();
|
||||
registry.add('.grandchild', 'command', () => 1);
|
||||
registry.add('.grandchild', 'command', () => Promise.resolve(2));
|
||||
registry.add('.grandchild', 'command', () => new Promise((resolve) => {
|
||||
setTimeout(() => { resolve(3); }, 1);
|
||||
}));
|
||||
|
||||
const values = await registry.dispatch(grandchild, 'command');
|
||||
expect(values).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
it("returns a promise that rejects when a listener is rejected", async () => {
|
||||
jasmine.useRealClock();
|
||||
registry.add('.grandchild', 'command', () => 1);
|
||||
registry.add('.grandchild', 'command', () => Promise.resolve(2));
|
||||
registry.add('.grandchild', 'command', () => new Promise((resolve, reject) => {
|
||||
setTimeout(() => { reject(3); }, 1);
|
||||
}));
|
||||
|
||||
let value;
|
||||
try {
|
||||
value = await registry.dispatch(grandchild, 'command');
|
||||
} catch (err) {
|
||||
value = err;
|
||||
}
|
||||
expect(value).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("::getSnapshot and ::restoreSnapshot", () =>
|
||||
it("removes all command handlers except for those in the snapshot", () => {
|
||||
registry.add('.parent', 'namespace:command-1', () => {});
|
||||
registry.add('.child', 'namespace:command-2', () => {});
|
||||
const snapshot = registry.getSnapshot();
|
||||
registry.add('.grandchild', 'namespace:command-3', () => {});
|
||||
|
||||
expect(registry.findCommands({target: grandchild}).slice(0, 3)).toEqual([
|
||||
{name: 'namespace:command-3', displayName: 'Namespace: Command 3'},
|
||||
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'},
|
||||
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
|
||||
]);
|
||||
|
||||
registry.restoreSnapshot(snapshot);
|
||||
|
||||
expect(registry.findCommands({target: grandchild}).slice(0, 2)).toEqual([
|
||||
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'},
|
||||
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
|
||||
]);
|
||||
|
||||
registry.add('.grandchild', 'namespace:command-3', () => {});
|
||||
registry.restoreSnapshot(snapshot);
|
||||
|
||||
expect(registry.findCommands({target: grandchild}).slice(0, 2)).toEqual([
|
||||
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'},
|
||||
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
describe("::attach(rootNode)", () =>
|
||||
it("adds event listeners for any previously-added commands", () => {
|
||||
const registry2 = new CommandRegistry;
|
||||
|
||||
const commandSpy = jasmine.createSpy('command-callback');
|
||||
registry2.add('.grandchild', 'command-1', commandSpy);
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', {bubbles: true}));
|
||||
expect(commandSpy).not.toHaveBeenCalled();
|
||||
|
||||
registry2.attach(parent);
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', {bubbles: true}));
|
||||
expect(commandSpy).toHaveBeenCalled();
|
||||
])
|
||||
})
|
||||
);
|
||||
});
|
||||
})
|
||||
|
||||
describe('::dispatch(target, commandName)', () => {
|
||||
it('simulates invocation of the given command ', () => {
|
||||
let called = false
|
||||
registry.add('.grandchild', 'command', function (event) {
|
||||
expect(this).toBe(grandchild)
|
||||
expect(event.type).toBe('command')
|
||||
expect(event.eventPhase).toBe(Event.BUBBLING_PHASE)
|
||||
expect(event.target).toBe(grandchild)
|
||||
expect(event.currentTarget).toBe(grandchild)
|
||||
called = true
|
||||
})
|
||||
|
||||
registry.dispatch(grandchild, 'command')
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
|
||||
it('returns a promise if any listeners matched the command', () => {
|
||||
registry.add('.grandchild', 'command', () => {})
|
||||
|
||||
expect(registry.dispatch(grandchild, 'command').constructor.name).toBe(
|
||||
'Promise'
|
||||
)
|
||||
expect(registry.dispatch(grandchild, 'bogus')).toBe(null)
|
||||
expect(registry.dispatch(parent, 'command')).toBe(null)
|
||||
})
|
||||
|
||||
it('returns a promise that resolves when the listeners resolve', async () => {
|
||||
jasmine.useRealClock()
|
||||
registry.add('.grandchild', 'command', () => 1)
|
||||
registry.add('.grandchild', 'command', () => Promise.resolve(2))
|
||||
registry.add(
|
||||
'.grandchild',
|
||||
'command',
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(3)
|
||||
}, 1)
|
||||
})
|
||||
)
|
||||
|
||||
const values = await registry.dispatch(grandchild, 'command')
|
||||
expect(values).toEqual([3, 2, 1])
|
||||
})
|
||||
|
||||
it('returns a promise that rejects when a listener is rejected', async () => {
|
||||
jasmine.useRealClock()
|
||||
registry.add('.grandchild', 'command', () => 1)
|
||||
registry.add('.grandchild', 'command', () => Promise.resolve(2))
|
||||
registry.add(
|
||||
'.grandchild',
|
||||
'command',
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(3)
|
||||
}, 1)
|
||||
})
|
||||
)
|
||||
|
||||
let value
|
||||
try {
|
||||
value = await registry.dispatch(grandchild, 'command')
|
||||
} catch (err) {
|
||||
value = err
|
||||
}
|
||||
expect(value).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('::getSnapshot and ::restoreSnapshot', () =>
|
||||
it('removes all command handlers except for those in the snapshot', () => {
|
||||
registry.add('.parent', 'namespace:command-1', () => {})
|
||||
registry.add('.child', 'namespace:command-2', () => {})
|
||||
const snapshot = registry.getSnapshot()
|
||||
registry.add('.grandchild', 'namespace:command-3', () => {})
|
||||
|
||||
expect(registry.findCommands({ target: grandchild }).slice(0, 3)).toEqual(
|
||||
[
|
||||
{ name: 'namespace:command-3', displayName: 'Namespace: Command 3' },
|
||||
{ name: 'namespace:command-2', displayName: 'Namespace: Command 2' },
|
||||
{ name: 'namespace:command-1', displayName: 'Namespace: Command 1' }
|
||||
]
|
||||
)
|
||||
|
||||
registry.restoreSnapshot(snapshot)
|
||||
|
||||
expect(registry.findCommands({ target: grandchild }).slice(0, 2)).toEqual(
|
||||
[
|
||||
{ name: 'namespace:command-2', displayName: 'Namespace: Command 2' },
|
||||
{ name: 'namespace:command-1', displayName: 'Namespace: Command 1' }
|
||||
]
|
||||
)
|
||||
|
||||
registry.add('.grandchild', 'namespace:command-3', () => {})
|
||||
registry.restoreSnapshot(snapshot)
|
||||
|
||||
expect(registry.findCommands({ target: grandchild }).slice(0, 2)).toEqual(
|
||||
[
|
||||
{ name: 'namespace:command-2', displayName: 'Namespace: Command 2' },
|
||||
{ name: 'namespace:command-1', displayName: 'Namespace: Command 1' }
|
||||
]
|
||||
)
|
||||
}))
|
||||
|
||||
describe('::attach(rootNode)', () =>
|
||||
it('adds event listeners for any previously-added commands', () => {
|
||||
const registry2 = new CommandRegistry()
|
||||
|
||||
const commandSpy = jasmine.createSpy('command-callback')
|
||||
registry2.add('.grandchild', 'command-1', commandSpy)
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true }))
|
||||
expect(commandSpy).not.toHaveBeenCalled()
|
||||
|
||||
registry2.attach(parent)
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command-1', { bubbles: true }))
|
||||
expect(commandSpy).toHaveBeenCalled()
|
||||
}))
|
||||
})
|
||||
|
@ -1,4 +1,3 @@
|
||||
const {it, fit, ffit, beforeEach, afterEach, conditionPromise} = require('./async-spec-helpers')
|
||||
const fs = require('fs-plus')
|
||||
const path = require('path')
|
||||
const temp = require('temp').track()
|
||||
@ -42,22 +41,25 @@ describe('ConfigFile', () => {
|
||||
|
||||
const event = new Promise(resolve => configFile.onDidChange(resolve))
|
||||
|
||||
writeFileSync(filePath, dedent `
|
||||
writeFileSync(
|
||||
filePath,
|
||||
dedent`
|
||||
'*':
|
||||
foo: 'bar'
|
||||
|
||||
'javascript':
|
||||
foo: 'baz'
|
||||
`)
|
||||
`
|
||||
)
|
||||
|
||||
expect(await event).toEqual({
|
||||
'*': {foo: 'bar'},
|
||||
'javascript': {foo: 'baz'}
|
||||
'*': { foo: 'bar' },
|
||||
javascript: { foo: 'baz' }
|
||||
})
|
||||
|
||||
expect(configFile.get()).toEqual({
|
||||
'*': {foo: 'bar'},
|
||||
'javascript': {foo: 'baz'}
|
||||
'*': { foo: 'bar' },
|
||||
javascript: { foo: 'baz' }
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -69,25 +71,33 @@ describe('ConfigFile', () => {
|
||||
|
||||
const message = new Promise(resolve => configFile.onDidError(resolve))
|
||||
|
||||
writeFileSync(filePath, dedent `
|
||||
writeFileSync(
|
||||
filePath,
|
||||
dedent`
|
||||
um what?
|
||||
`, 2)
|
||||
`,
|
||||
2
|
||||
)
|
||||
|
||||
expect(await message).toContain('Failed to load `the-config.cson`')
|
||||
|
||||
const event = new Promise(resolve => configFile.onDidChange(resolve))
|
||||
|
||||
writeFileSync(filePath, dedent `
|
||||
writeFileSync(
|
||||
filePath,
|
||||
dedent`
|
||||
'*':
|
||||
foo: 'bar'
|
||||
|
||||
'javascript':
|
||||
foo: 'baz'
|
||||
`, 4)
|
||||
`,
|
||||
4
|
||||
)
|
||||
|
||||
expect(await event).toEqual({
|
||||
'*': {foo: 'bar'},
|
||||
'javascript': {foo: 'baz'}
|
||||
'*': { foo: 'bar' },
|
||||
javascript: { foo: 'baz' }
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -115,7 +125,7 @@ describe('ConfigFile', () => {
|
||||
})
|
||||
|
||||
function writeFileSync (filePath, content, seconds = 2) {
|
||||
const utime = (Date.now() / 1000) + seconds
|
||||
const utime = Date.now() / 1000 + seconds
|
||||
fs.writeFileSync(filePath, content)
|
||||
fs.utimesSync(filePath, utime, utime)
|
||||
}
|
||||
|
1107
spec/config-spec.js
1107
spec/config-spec.js
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
DecorationManager = require '../src/decoration-manager'
|
||||
TextEditor = require '../src/text-editor'
|
||||
|
||||
# Tests crash the renderer process on Electron 3.0, disabling for now.
|
||||
describe "DecorationManager", ->
|
||||
[decorationManager, buffer, editor, markerLayer1, markerLayer2] = []
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
const Grim = require('grim')
|
||||
|
||||
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
|
||||
import etch from 'etch'
|
||||
|
||||
const getNextUpdatePromise = () => etch.getScheduler().nextUpdatePromise
|
||||
@ -16,7 +15,12 @@ describe('Dock', () => {
|
||||
dock.onDidChangeVisible(didChangeVisibleSpy)
|
||||
|
||||
expect(dock.isVisible()).toBe(false)
|
||||
expect(document.activeElement).toBe(atom.workspace.getCenter().getActivePane().getElement())
|
||||
expect(document.activeElement).toBe(
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getActivePane()
|
||||
.getElement()
|
||||
)
|
||||
dock.activate()
|
||||
expect(dock.isVisible()).toBe(true)
|
||||
expect(document.activeElement).toBe(dock.getActivePane().getElement())
|
||||
@ -36,7 +40,12 @@ describe('Dock', () => {
|
||||
expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true)
|
||||
|
||||
dock.hide()
|
||||
expect(document.activeElement).toBe(atom.workspace.getCenter().getActivePane().getElement())
|
||||
expect(document.activeElement).toBe(
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getActivePane()
|
||||
.getElement()
|
||||
)
|
||||
expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false)
|
||||
|
||||
dock.activate()
|
||||
@ -44,13 +53,18 @@ describe('Dock', () => {
|
||||
expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true)
|
||||
|
||||
dock.toggle()
|
||||
expect(document.activeElement).toBe(atom.workspace.getCenter().getActivePane().getElement())
|
||||
expect(document.activeElement).toBe(
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getActivePane()
|
||||
.getElement()
|
||||
)
|
||||
expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false)
|
||||
|
||||
// Don't change focus if the dock was not focused in the first place
|
||||
const modalElement = document.createElement('div')
|
||||
modalElement.setAttribute('tabindex', -1)
|
||||
atom.workspace.addModalPanel({item: modalElement})
|
||||
atom.workspace.addModalPanel({ item: modalElement })
|
||||
modalElement.focus()
|
||||
expect(document.activeElement).toBe(modalElement)
|
||||
|
||||
@ -68,13 +82,18 @@ describe('Dock', () => {
|
||||
it('opens the dock', async () => {
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'left' }
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
}
|
||||
}
|
||||
|
||||
await atom.workspace.open(item, {activatePane: false})
|
||||
await atom.workspace.open(item, { activatePane: false })
|
||||
expect(atom.workspace.getLeftDock().isVisible()).toBe(false)
|
||||
|
||||
atom.workspace.getLeftDock().getPanes()[0].activate()
|
||||
atom.workspace
|
||||
.getLeftDock()
|
||||
.getPanes()[0]
|
||||
.activate()
|
||||
expect(atom.workspace.getLeftDock().isVisible()).toBe(true)
|
||||
})
|
||||
})
|
||||
@ -145,47 +164,63 @@ describe('Dock', () => {
|
||||
|
||||
describe('when the dock resize handle is double-clicked', () => {
|
||||
describe('when the dock is open', () => {
|
||||
it('resizes a vertically-oriented dock to the current item\'s preferred width', async () => {
|
||||
it("resizes a vertically-oriented dock to the current item's preferred width", async () => {
|
||||
jasmine.attachToDOM(atom.workspace.getElement())
|
||||
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'left' },
|
||||
getPreferredWidth() { return 142 },
|
||||
getPreferredHeight() { return 122 }
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return 142
|
||||
},
|
||||
getPreferredHeight () {
|
||||
return 122
|
||||
}
|
||||
}
|
||||
|
||||
await atom.workspace.open(item)
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
const dockElement = dock.getElement()
|
||||
|
||||
dock.setState({size: 300})
|
||||
dock.setState({ size: 300 })
|
||||
await getNextUpdatePromise()
|
||||
expect(dockElement.offsetWidth).toBe(300)
|
||||
dockElement.querySelector('.atom-dock-resize-handle').dispatchEvent(new MouseEvent('mousedown', {detail: 2}))
|
||||
dockElement
|
||||
.querySelector('.atom-dock-resize-handle')
|
||||
.dispatchEvent(new MouseEvent('mousedown', { detail: 2 }))
|
||||
await getNextUpdatePromise()
|
||||
|
||||
expect(dockElement.offsetWidth).toBe(item.getPreferredWidth())
|
||||
})
|
||||
|
||||
it('resizes a horizontally-oriented dock to the current item\'s preferred width', async () => {
|
||||
it("resizes a horizontally-oriented dock to the current item's preferred width", async () => {
|
||||
jasmine.attachToDOM(atom.workspace.getElement())
|
||||
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'bottom' },
|
||||
getPreferredWidth() { return 122 },
|
||||
getPreferredHeight() { return 142 }
|
||||
getDefaultLocation () {
|
||||
return 'bottom'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return 122
|
||||
},
|
||||
getPreferredHeight () {
|
||||
return 142
|
||||
}
|
||||
}
|
||||
|
||||
await atom.workspace.open(item)
|
||||
const dock = atom.workspace.getBottomDock()
|
||||
const dockElement = dock.getElement()
|
||||
|
||||
dock.setState({size: 300})
|
||||
dock.setState({ size: 300 })
|
||||
await getNextUpdatePromise()
|
||||
expect(dockElement.offsetHeight).toBe(300)
|
||||
dockElement.querySelector('.atom-dock-resize-handle').dispatchEvent(new MouseEvent('mousedown', {detail: 2}))
|
||||
dockElement
|
||||
.querySelector('.atom-dock-resize-handle')
|
||||
.dispatchEvent(new MouseEvent('mousedown', { detail: 2 }))
|
||||
await getNextUpdatePromise()
|
||||
|
||||
expect(dockElement.offsetHeight).toBe(item.getPreferredHeight())
|
||||
@ -198,19 +233,31 @@ describe('Dock', () => {
|
||||
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'bottom' },
|
||||
getPreferredWidth() { return 122 },
|
||||
getPreferredHeight() { return 142 }
|
||||
getDefaultLocation () {
|
||||
return 'bottom'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return 122
|
||||
},
|
||||
getPreferredHeight () {
|
||||
return 142
|
||||
}
|
||||
}
|
||||
|
||||
await atom.workspace.open(item, {activatePane: false})
|
||||
await atom.workspace.open(item, { activatePane: false })
|
||||
|
||||
const dockElement = atom.workspace.getBottomDock().getElement()
|
||||
dockElement.querySelector('.atom-dock-resize-handle').dispatchEvent(new MouseEvent('mousedown', {detail: 2}))
|
||||
dockElement
|
||||
.querySelector('.atom-dock-resize-handle')
|
||||
.dispatchEvent(new MouseEvent('mousedown', { detail: 2 }))
|
||||
expect(dockElement.offsetHeight).toBe(0)
|
||||
expect(dockElement.querySelector('.atom-dock-inner').offsetHeight).toBe(0)
|
||||
expect(dockElement.querySelector('.atom-dock-inner').offsetHeight).toBe(
|
||||
0
|
||||
)
|
||||
// The content should be masked away.
|
||||
expect(dockElement.querySelector('.atom-dock-mask').offsetHeight).toBe(0)
|
||||
expect(dockElement.querySelector('.atom-dock-mask').offsetHeight).toBe(
|
||||
0
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -222,8 +269,12 @@ describe('Dock', () => {
|
||||
|
||||
const createItem = preferredWidth => ({
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'left' },
|
||||
getPreferredWidth() { return preferredWidth }
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return preferredWidth
|
||||
}
|
||||
})
|
||||
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
@ -257,7 +308,9 @@ describe('Dock', () => {
|
||||
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'left' }
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
}
|
||||
}
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
expect(dock.getPaneItems()).toHaveLength(0)
|
||||
@ -275,11 +328,15 @@ describe('Dock', () => {
|
||||
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'left' },
|
||||
getPreferredWidth() { return 122 },
|
||||
serialize: () => ({deserializer: 'DockTestItem'})
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return 122
|
||||
},
|
||||
serialize: () => ({ deserializer: 'DockTestItem' })
|
||||
}
|
||||
const itemDeserializer = atom.deserializers.add({
|
||||
atom.deserializers.add({
|
||||
name: 'DockTestItem',
|
||||
deserialize: () => item
|
||||
})
|
||||
@ -287,10 +344,10 @@ describe('Dock', () => {
|
||||
const dockElement = dock.getElement()
|
||||
|
||||
await atom.workspace.open(item)
|
||||
dock.setState({size: 150})
|
||||
dock.setState({ size: 150 })
|
||||
expect(dockElement.offsetWidth).toBe(150)
|
||||
const serialized = dock.serialize()
|
||||
dock.setState({size: 122})
|
||||
dock.setState({ size: 122 })
|
||||
expect(dockElement.offsetWidth).toBe(122)
|
||||
dock.destroyActivePane()
|
||||
dock.deserialize(serialized, atom.deserializers)
|
||||
@ -302,8 +359,12 @@ describe('Dock', () => {
|
||||
|
||||
const item = {
|
||||
element: document.createElement('div'),
|
||||
getDefaultLocation() { return 'left' },
|
||||
getPreferredWidth() { return 122 }
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return 122
|
||||
}
|
||||
}
|
||||
const dock = atom.workspace.getLeftDock()
|
||||
|
||||
@ -324,16 +385,22 @@ describe('Dock', () => {
|
||||
element.setAttribute('is', 'tabs-tab')
|
||||
element.item = {
|
||||
element,
|
||||
getDefaultLocation() { return 'left' },
|
||||
getPreferredWidth() { return 144 }
|
||||
getDefaultLocation () {
|
||||
return 'left'
|
||||
},
|
||||
getPreferredWidth () {
|
||||
return 144
|
||||
}
|
||||
}
|
||||
|
||||
const dragEvent = new DragEvent('dragstart')
|
||||
Object.defineProperty(dragEvent, 'target', {value: element})
|
||||
Object.defineProperty(dragEvent, 'target', { value: element })
|
||||
|
||||
atom.workspace.getElement().handleDragStart(dragEvent)
|
||||
await getNextUpdatePromise()
|
||||
expect(atom.workspace.getLeftDock().refs.wrapperElement.offsetWidth).toBe(144)
|
||||
expect(atom.workspace.getLeftDock().refs.wrapperElement.offsetWidth).toBe(
|
||||
144
|
||||
)
|
||||
})
|
||||
|
||||
it('does nothing when text nodes are dragged', () => {
|
||||
@ -342,9 +409,11 @@ describe('Dock', () => {
|
||||
const textNode = document.createTextNode('hello')
|
||||
|
||||
const dragEvent = new DragEvent('dragstart')
|
||||
Object.defineProperty(dragEvent, 'target', {value: textNode})
|
||||
Object.defineProperty(dragEvent, 'target', { value: textNode })
|
||||
|
||||
expect(() => atom.workspace.getElement().handleDragStart(dragEvent)).not.toThrow()
|
||||
expect(() =>
|
||||
atom.workspace.getElement().handleDragStart(dragEvent)
|
||||
).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
|
2
spec/fixtures/babel/invalid.js
vendored
2
spec/fixtures/babel/invalid.js
vendored
@ -1,3 +1,3 @@
|
||||
'use 6to6';
|
||||
|
||||
export default 42;
|
||||
module.exports = async function* hello() {}
|
||||
|
@ -1,16 +1,19 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const temp = require('temp').track()
|
||||
const {Directory} = require('pathwatcher')
|
||||
const { Directory } = require('pathwatcher')
|
||||
const GitRepository = require('../src/git-repository')
|
||||
const GitRepositoryProvider = require('../src/git-repository-provider')
|
||||
const {it, fit, ffit, fffit, beforeEach} = require('./async-spec-helpers')
|
||||
|
||||
describe('GitRepositoryProvider', () => {
|
||||
let provider
|
||||
|
||||
beforeEach(() => {
|
||||
provider = new GitRepositoryProvider(atom.project, atom.config, atom.confirm)
|
||||
provider = new GitRepositoryProvider(
|
||||
atom.project,
|
||||
atom.config,
|
||||
atom.confirm
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -24,7 +27,9 @@ describe('GitRepositoryProvider', () => {
|
||||
describe('.repositoryForDirectory(directory)', () => {
|
||||
describe('when specified a Directory with a Git repository', () => {
|
||||
it('resolves with a GitRepository', async () => {
|
||||
const directory = new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
const directory = new Directory(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
)
|
||||
const result = await provider.repositoryForDirectory(directory)
|
||||
expect(result).toBeInstanceOf(GitRepository)
|
||||
expect(provider.pathToRepository[result.getPath()]).toBeTruthy()
|
||||
@ -39,7 +44,9 @@ describe('GitRepositoryProvider', () => {
|
||||
new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
)
|
||||
const secondRepo = await provider.repositoryForDirectory(
|
||||
new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects'))
|
||||
new Directory(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects')
|
||||
)
|
||||
)
|
||||
|
||||
expect(firstRepo).toBeInstanceOf(GitRepository)
|
||||
@ -72,7 +79,10 @@ describe('GitRepositoryProvider', () => {
|
||||
it('returns a Promise that resolves to a GitRepository', async () => {
|
||||
const gitDirPath = path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
const workDirPath = temp.mkdirSync('git-workdir')
|
||||
fs.writeFileSync(path.join(workDirPath, '.git'), `gitdir: ${gitDirPath}\n`)
|
||||
fs.writeFileSync(
|
||||
path.join(workDirPath, '.git'),
|
||||
`gitdir: ${gitDirPath}\n`
|
||||
)
|
||||
|
||||
const directory = new Directory(workDirPath)
|
||||
const result = await provider.repositoryForDirectory(directory)
|
||||
@ -82,6 +92,90 @@ describe('GitRepositoryProvider', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specified a Directory without exists()', () => {
|
||||
let directory
|
||||
|
||||
beforeEach(() => {
|
||||
// An implementation of Directory that does not implement existsSync().
|
||||
const subdirectory = {}
|
||||
directory = {
|
||||
getSubdirectory () {},
|
||||
isRoot () {
|
||||
return true
|
||||
}
|
||||
}
|
||||
spyOn(directory, 'getSubdirectory').andReturn(subdirectory)
|
||||
})
|
||||
|
||||
it('returns a Promise that resolves to null', async () => {
|
||||
const repo = await provider.repositoryForDirectory(directory)
|
||||
expect(repo).toBe(null)
|
||||
expect(directory.getSubdirectory).toHaveBeenCalledWith('.git')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.repositoryForDirectorySync(directory)', () => {
|
||||
describe('when specified a Directory with a Git repository', () => {
|
||||
it('resolves with a GitRepository', async () => {
|
||||
const directory = new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
const result = provider.repositoryForDirectorySync(directory)
|
||||
expect(result).toBeInstanceOf(GitRepository)
|
||||
expect(provider.pathToRepository[result.getPath()]).toBeTruthy()
|
||||
expect(result.getType()).toBe('git')
|
||||
|
||||
// Refresh should be started
|
||||
await new Promise(resolve => result.onDidChangeStatuses(resolve))
|
||||
})
|
||||
|
||||
it('resolves with the same GitRepository for different Directory objects in the same repo', () => {
|
||||
const firstRepo = provider.repositoryForDirectorySync(
|
||||
new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
)
|
||||
const secondRepo = provider.repositoryForDirectorySync(
|
||||
new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects'))
|
||||
)
|
||||
|
||||
expect(firstRepo).toBeInstanceOf(GitRepository)
|
||||
expect(firstRepo).toBe(secondRepo)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specified a Directory without a Git repository', () => {
|
||||
it('resolves with null', () => {
|
||||
const directory = new Directory(temp.mkdirSync('dir'))
|
||||
const repo = provider.repositoryForDirectorySync(directory)
|
||||
expect(repo).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specified a Directory with an invalid Git repository', () => {
|
||||
it('resolves with null', () => {
|
||||
const dirPath = temp.mkdirSync('dir')
|
||||
fs.writeFileSync(path.join(dirPath, '.git', 'objects'), '')
|
||||
fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), '')
|
||||
fs.writeFileSync(path.join(dirPath, '.git', 'refs'), '')
|
||||
|
||||
const directory = new Directory(dirPath)
|
||||
const repo = provider.repositoryForDirectorySync(directory)
|
||||
expect(repo).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specified a Directory with a valid gitfile-linked repository', () => {
|
||||
it('returns a Promise that resolves to a GitRepository', () => {
|
||||
const gitDirPath = path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
const workDirPath = temp.mkdirSync('git-workdir')
|
||||
fs.writeFileSync(path.join(workDirPath, '.git'), `gitdir: ${gitDirPath}\n`)
|
||||
|
||||
const directory = new Directory(workDirPath)
|
||||
const result = provider.repositoryForDirectorySync(directory)
|
||||
expect(result).toBeInstanceOf(GitRepository)
|
||||
expect(provider.pathToRepository[result.getPath()]).toBeTruthy()
|
||||
expect(result.getType()).toBe('git')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specified a Directory without existsSync()', () => {
|
||||
let directory
|
||||
|
||||
@ -100,12 +194,6 @@ describe('GitRepositoryProvider', () => {
|
||||
expect(repo).toBe(null)
|
||||
expect(directory.getSubdirectory).toHaveBeenCalledWith('.git')
|
||||
})
|
||||
|
||||
it('returns a Promise that resolves to null for the async implementation', async () => {
|
||||
const repo = await provider.repositoryForDirectory(directory)
|
||||
expect(repo).toBe(null)
|
||||
expect(directory.getSubdirectory).toHaveBeenCalledWith('.git')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,3 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const temp = require('temp').track()
|
||||
@ -25,30 +24,44 @@ describe('GitRepository', () => {
|
||||
|
||||
describe('new GitRepository(path)', () => {
|
||||
it('throws an exception when no repository is found', () => {
|
||||
expect(() => new GitRepository(path.join(temp.dir, 'nogit.txt'))).toThrow()
|
||||
expect(
|
||||
() => new GitRepository(path.join(temp.dir, 'nogit.txt'))
|
||||
).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getPath()', () => {
|
||||
it('returns the repository path for a .git directory path with a directory', () => {
|
||||
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects'))
|
||||
expect(repo.getPath()).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
repo = new GitRepository(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects')
|
||||
)
|
||||
expect(repo.getPath()).toBe(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
)
|
||||
})
|
||||
|
||||
it('returns the repository path for a repository path', () => {
|
||||
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
expect(repo.getPath()).toBe(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
repo = new GitRepository(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
)
|
||||
expect(repo.getPath()).toBe(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isPathIgnored(path)', () => {
|
||||
it('returns true for an ignored path', () => {
|
||||
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'ignore.git'))
|
||||
repo = new GitRepository(
|
||||
path.join(__dirname, 'fixtures', 'git', 'ignore.git')
|
||||
)
|
||||
expect(repo.isPathIgnored('a.txt')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('returns false for a non-ignored path', () => {
|
||||
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'ignore.git'))
|
||||
repo = new GitRepository(
|
||||
path.join(__dirname, 'fixtures', 'git', 'ignore.git')
|
||||
)
|
||||
expect(repo.isPathIgnored('b.txt')).toBeFalsy()
|
||||
})
|
||||
})
|
||||
@ -136,7 +149,10 @@ describe('GitRepository', () => {
|
||||
repo.onDidChangeStatus(statusHandler)
|
||||
repo.checkoutHead(filePath)
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: 0})
|
||||
expect(statusHandler.argsForCall[0][0]).toEqual({
|
||||
path: filePath,
|
||||
pathStatus: 0
|
||||
})
|
||||
|
||||
repo.checkoutHead(filePath)
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
@ -150,7 +166,11 @@ describe('GitRepository', () => {
|
||||
spyOn(atom, 'confirm')
|
||||
|
||||
const workingDirPath = copyRepository()
|
||||
repo = new GitRepository(workingDirPath, {project: atom.project, config: atom.config, confirm: atom.confirm})
|
||||
repo = new GitRepository(workingDirPath, {
|
||||
project: atom.project,
|
||||
config: atom.config,
|
||||
confirm: atom.confirm
|
||||
})
|
||||
filePath = path.join(workingDirPath, 'a.txt')
|
||||
fs.writeFileSync(filePath, 'ch ch changes')
|
||||
|
||||
@ -161,7 +181,7 @@ describe('GitRepository', () => {
|
||||
// Permissions issues with this test on Windows
|
||||
if (process.platform === 'win32') return
|
||||
|
||||
atom.confirm.andCallFake(({buttons}) => buttons.OK())
|
||||
atom.confirm.andCallFake(({ buttons }) => buttons.OK())
|
||||
atom.config.set('editor.confirmCheckoutHeadRevision', true)
|
||||
|
||||
repo.checkoutHeadForEditor(editor)
|
||||
@ -183,7 +203,9 @@ describe('GitRepository', () => {
|
||||
|
||||
describe('.destroy()', () => {
|
||||
it('throws an exception when any method is called after it is called', () => {
|
||||
repo = new GitRepository(path.join(__dirname, 'fixtures', 'git', 'master.git'))
|
||||
repo = new GitRepository(
|
||||
path.join(__dirname, 'fixtures', 'git', 'master.git')
|
||||
)
|
||||
repo.destroy()
|
||||
expect(() => repo.getShortHead()).toThrow()
|
||||
})
|
||||
@ -204,7 +226,10 @@ describe('GitRepository', () => {
|
||||
fs.writeFileSync(filePath, '')
|
||||
let status = repo.getPathStatus(filePath)
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(statusHandler.argsForCall[0][0]).toEqual({path: filePath, pathStatus: status})
|
||||
expect(statusHandler.argsForCall[0][0]).toEqual({
|
||||
path: filePath,
|
||||
pathStatus: status
|
||||
})
|
||||
|
||||
fs.writeFileSync(filePath, 'abc')
|
||||
status = repo.getPathStatus(filePath)
|
||||
@ -223,10 +248,14 @@ describe('GitRepository', () => {
|
||||
})
|
||||
|
||||
it('gets the status based on the files inside the directory', () => {
|
||||
expect(repo.isStatusModified(repo.getDirectoryStatus(directoryPath))).toBe(false)
|
||||
expect(
|
||||
repo.isStatusModified(repo.getDirectoryStatus(directoryPath))
|
||||
).toBe(false)
|
||||
fs.writeFileSync(filePath, 'abc')
|
||||
repo.getPathStatus(filePath)
|
||||
expect(repo.isStatusModified(repo.getDirectoryStatus(directoryPath))).toBe(true)
|
||||
expect(
|
||||
repo.isStatusModified(repo.getDirectoryStatus(directoryPath))
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -235,7 +264,10 @@ describe('GitRepository', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
workingDirectory = copyRepository()
|
||||
repo = new GitRepository(workingDirectory, {project: atom.project, config: atom.config})
|
||||
repo = new GitRepository(workingDirectory, {
|
||||
project: atom.project,
|
||||
config: atom.config
|
||||
})
|
||||
modifiedPath = path.join(workingDirectory, 'file.txt')
|
||||
newPath = path.join(workingDirectory, 'untracked.txt')
|
||||
cleanPath = path.join(workingDirectory, 'other.txt')
|
||||
@ -252,8 +284,10 @@ describe('GitRepository', () => {
|
||||
await repo.refreshStatus()
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined()
|
||||
expect(repo.isStatusNew(repo.getCachedPathStatus(newPath) )).toBeTruthy()
|
||||
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()
|
||||
expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
|
||||
expect(
|
||||
repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('caches the proper statuses when a subdir is open', async () => {
|
||||
@ -278,7 +312,9 @@ describe('GitRepository', () => {
|
||||
await repo.refreshStatus()
|
||||
expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined()
|
||||
expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
|
||||
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()
|
||||
expect(
|
||||
repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('caches statuses that were looked up synchronously', async () => {
|
||||
@ -288,7 +324,9 @@ describe('GitRepository', () => {
|
||||
|
||||
fs.writeFileSync(modifiedPath, originalContent)
|
||||
await repo.refreshStatus()
|
||||
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeFalsy()
|
||||
expect(
|
||||
repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))
|
||||
).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
@ -297,7 +335,9 @@ describe('GitRepository', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
atom.project.setPaths([copyRepository()])
|
||||
const refreshPromise = new Promise(resolve => atom.project.getRepositories()[0].onDidChangeStatuses(resolve))
|
||||
const refreshPromise = new Promise(resolve =>
|
||||
atom.project.getRepositories()[0].onDidChangeStatuses(resolve)
|
||||
)
|
||||
editor = await atom.workspace.open('other.txt')
|
||||
await refreshPromise
|
||||
})
|
||||
@ -310,7 +350,10 @@ describe('GitRepository', () => {
|
||||
|
||||
await editor.save()
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})
|
||||
expect(statusHandler).toHaveBeenCalledWith({
|
||||
path: editor.getPath(),
|
||||
pathStatus: 256
|
||||
})
|
||||
})
|
||||
|
||||
it('emits a status-changed event when a buffer is reloaded', async () => {
|
||||
@ -321,7 +364,10 @@ describe('GitRepository', () => {
|
||||
|
||||
await editor.getBuffer().reload()
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})
|
||||
expect(statusHandler).toHaveBeenCalledWith({
|
||||
path: editor.getPath(),
|
||||
pathStatus: 256
|
||||
})
|
||||
|
||||
await editor.getBuffer().reload()
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
@ -334,7 +380,10 @@ describe('GitRepository', () => {
|
||||
atom.project.getRepositories()[0].onDidChangeStatus(statusHandler)
|
||||
editor.getBuffer().emitter.emit('did-change-path')
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(statusHandler).toHaveBeenCalledWith({path: editor.getPath(), pathStatus: 256})
|
||||
expect(statusHandler).toHaveBeenCalledWith({
|
||||
path: editor.getPath(),
|
||||
pathStatus: 256
|
||||
})
|
||||
editor.getBuffer().emitter.emit('did-change-path')
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
})
|
||||
@ -364,11 +413,9 @@ describe('GitRepository', () => {
|
||||
grammarRegistry: atom.grammars,
|
||||
applicationDelegate: atom.applicationDelegate
|
||||
})
|
||||
await project2.deserialize(atom.project.serialize({isUnloading: false}))
|
||||
await project2.deserialize(atom.project.serialize({ isUnloading: false }))
|
||||
|
||||
buffer = project2.getBuffers()[0]
|
||||
|
||||
const originalContent = buffer.getText()
|
||||
buffer.append('changes')
|
||||
|
||||
statusHandler = jasmine.createSpy('statusHandler')
|
||||
@ -376,14 +423,23 @@ describe('GitRepository', () => {
|
||||
await buffer.save()
|
||||
|
||||
expect(statusHandler.callCount).toBe(1)
|
||||
expect(statusHandler).toHaveBeenCalledWith({path: buffer.getPath(), pathStatus: 256})
|
||||
expect(statusHandler).toHaveBeenCalledWith({
|
||||
path: buffer.getPath(),
|
||||
pathStatus: 256
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function copyRepository () {
|
||||
const workingDirPath = temp.mkdirSync('atom-spec-git')
|
||||
fs.copySync(path.join(__dirname, 'fixtures', 'git', 'working-dir'), workingDirPath)
|
||||
fs.renameSync(path.join(workingDirPath, 'git.git'), path.join(workingDirPath, '.git'))
|
||||
fs.copySync(
|
||||
path.join(__dirname, 'fixtures', 'git', 'working-dir'),
|
||||
workingDirPath
|
||||
)
|
||||
fs.renameSync(
|
||||
path.join(workingDirPath, 'git.git'),
|
||||
path.join(workingDirPath, '.git')
|
||||
)
|
||||
return workingDirPath
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
|
||||
const dedent = require('dedent')
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
@ -13,14 +11,18 @@ describe('GrammarRegistry', () => {
|
||||
let grammarRegistry
|
||||
|
||||
beforeEach(() => {
|
||||
grammarRegistry = new GrammarRegistry({config: atom.config})
|
||||
grammarRegistry = new GrammarRegistry({ config: atom.config })
|
||||
expect(subscriptionCount(grammarRegistry)).toBe(1)
|
||||
})
|
||||
|
||||
describe('.assignLanguageMode(buffer, languageId)', () => {
|
||||
it('assigns to the buffer a language mode with the given language id', async () => {
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-css/grammars/css.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-css/grammars/css.cson')
|
||||
)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe(true)
|
||||
@ -31,7 +33,9 @@ describe('GrammarRegistry', () => {
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.js')).toBe(true)
|
||||
|
||||
// Language names are not case-sensitive
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(true)
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(
|
||||
true
|
||||
)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css')
|
||||
|
||||
// Returns false if no language is found
|
||||
@ -41,14 +45,20 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
describe('when no languageId is passed', () => {
|
||||
it('makes the buffer use the null grammar', () => {
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-css/grammars/css.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-css/grammars/css.cson')
|
||||
)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(true)
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(
|
||||
true
|
||||
)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css')
|
||||
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, null)).toBe(true)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(
|
||||
'text.plain.null-grammar'
|
||||
)
|
||||
expect(grammarRegistry.getAssignedLanguageId(buffer)).toBe(null)
|
||||
})
|
||||
})
|
||||
@ -56,10 +66,18 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
describe('.grammarForId(languageId)', () => {
|
||||
it('returns a text-mate grammar when `core.useTreeSitterParsers` is false', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', false, {scopeSelector: '.source.js'})
|
||||
atom.config.set('core.useTreeSitterParsers', false, {
|
||||
scopeSelector: '.source.js'
|
||||
})
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
|
||||
const grammar = grammarRegistry.grammarForId('source.js')
|
||||
expect(grammar instanceof FirstMate.Grammar).toBe(true)
|
||||
@ -72,26 +90,40 @@ describe('GrammarRegistry', () => {
|
||||
it('returns a tree-sitter grammar when `core.useTreeSitterParsers` is true', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
|
||||
const grammar = grammarRegistry.grammarForId('source.js')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBe(true)
|
||||
expect(grammar.scopeName).toBe('source.js')
|
||||
|
||||
grammarRegistry.removeGrammar(grammar)
|
||||
expect(grammarRegistry.grammarForId('source.js') instanceof FirstMate.Grammar).toBe(true)
|
||||
expect(
|
||||
grammarRegistry.grammarForId('source.js') instanceof FirstMate.Grammar
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.autoAssignLanguageMode(buffer)', () => {
|
||||
it('assigns to the buffer a language mode based on the best available grammar', () => {
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-css/grammars/css.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-css/grammars/css.cson')
|
||||
)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
buffer.setPath('foo.js')
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(true)
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(
|
||||
true
|
||||
)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css')
|
||||
|
||||
grammarRegistry.autoAssignLanguageMode(buffer)
|
||||
@ -103,8 +135,12 @@ describe('GrammarRegistry', () => {
|
||||
it('assigns a grammar to the buffer based on its path', async () => {
|
||||
const buffer = new TextBuffer()
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/c.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/c.cson')
|
||||
)
|
||||
|
||||
buffer.setPath('test.js')
|
||||
grammarRegistry.maintainLanguageMode(buffer)
|
||||
@ -114,7 +150,7 @@ describe('GrammarRegistry', () => {
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.c')
|
||||
})
|
||||
|
||||
it('updates the buffer\'s grammar when a more appropriate text-mate grammar is added for its path', async () => {
|
||||
it("updates the buffer's grammar when a more appropriate text-mate grammar is added for its path", async () => {
|
||||
atom.config.set('core.useTreeSitterParsers', false)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
@ -123,14 +159,20 @@ describe('GrammarRegistry', () => {
|
||||
buffer.setPath('test.js')
|
||||
grammarRegistry.maintainLanguageMode(buffer)
|
||||
|
||||
const textMateGrammar = grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
const textMateGrammar = grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar)
|
||||
})
|
||||
|
||||
it('updates the buffer\'s grammar when a more appropriate tree-sitter grammar is added for its path', async () => {
|
||||
it("updates the buffer's grammar when a more appropriate tree-sitter grammar is added for its path", async () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
@ -139,10 +181,16 @@ describe('GrammarRegistry', () => {
|
||||
buffer.setPath('test.js')
|
||||
grammarRegistry.maintainLanguageMode(buffer)
|
||||
|
||||
const treeSitterGrammar = grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
const treeSitterGrammar = grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar)
|
||||
})
|
||||
|
||||
@ -152,41 +200,59 @@ describe('GrammarRegistry', () => {
|
||||
buffer.setPath('test.js')
|
||||
grammarRegistry.maintainLanguageMode(buffer)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-css/grammars/css.cson'))
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(true)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-css/grammars/css.cson')
|
||||
)
|
||||
expect(grammarRegistry.assignLanguageMode(buffer, 'source.css')).toBe(
|
||||
true
|
||||
)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css')
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css')
|
||||
})
|
||||
|
||||
it('returns a disposable that can be used to stop the registry from updating the buffer', async () => {
|
||||
const buffer = new TextBuffer()
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
|
||||
const previousSubscriptionCount = buffer.emitter.getTotalListenerCount()
|
||||
const disposable = grammarRegistry.maintainLanguageMode(buffer)
|
||||
expect(buffer.emitter.getTotalListenerCount()).toBeGreaterThan(previousSubscriptionCount)
|
||||
expect(buffer.emitter.getTotalListenerCount()).toBeGreaterThan(
|
||||
previousSubscriptionCount
|
||||
)
|
||||
expect(retainedBufferCount(grammarRegistry)).toBe(1)
|
||||
|
||||
buffer.setPath('test.js')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js')
|
||||
|
||||
buffer.setPath('test.txt')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(
|
||||
'text.plain.null-grammar'
|
||||
)
|
||||
|
||||
disposable.dispose()
|
||||
expect(buffer.emitter.getTotalListenerCount()).toBe(previousSubscriptionCount)
|
||||
expect(buffer.emitter.getTotalListenerCount()).toBe(
|
||||
previousSubscriptionCount
|
||||
)
|
||||
expect(retainedBufferCount(grammarRegistry)).toBe(0)
|
||||
|
||||
buffer.setPath('test.js')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(
|
||||
'text.plain.null-grammar'
|
||||
)
|
||||
expect(retainedBufferCount(grammarRegistry)).toBe(0)
|
||||
})
|
||||
|
||||
it('doesn\'t do anything when called a second time with the same buffer', async () => {
|
||||
it("doesn't do anything when called a second time with the same buffer", async () => {
|
||||
const buffer = new TextBuffer()
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
const disposable1 = grammarRegistry.maintainLanguageMode(buffer)
|
||||
const disposable2 = grammarRegistry.maintainLanguageMode(buffer)
|
||||
|
||||
@ -195,16 +261,22 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
disposable2.dispose()
|
||||
buffer.setPath('test.txt')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(
|
||||
'text.plain.null-grammar'
|
||||
)
|
||||
|
||||
disposable1.dispose()
|
||||
buffer.setPath('test.js')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('text.plain.null-grammar')
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(
|
||||
'text.plain.null-grammar'
|
||||
)
|
||||
})
|
||||
|
||||
it('does not retain the buffer after the buffer is destroyed', () => {
|
||||
const buffer = new TextBuffer()
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
|
||||
const disposable = grammarRegistry.maintainLanguageMode(buffer)
|
||||
expect(retainedBufferCount(grammarRegistry)).toBe(1)
|
||||
@ -222,9 +294,11 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
it('does not retain the buffer when the grammar registry is destroyed', () => {
|
||||
const buffer = new TextBuffer()
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
|
||||
const disposable = grammarRegistry.maintainLanguageMode(buffer)
|
||||
grammarRegistry.maintainLanguageMode(buffer)
|
||||
expect(retainedBufferCount(grammarRegistry)).toBe(1)
|
||||
expect(subscriptionCount(grammarRegistry)).toBe(3)
|
||||
|
||||
@ -238,19 +312,25 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
describe('.selectGrammar(filePath)', () => {
|
||||
it('always returns a grammar', () => {
|
||||
const registry = new GrammarRegistry({config: atom.config})
|
||||
const registry = new GrammarRegistry({ config: atom.config })
|
||||
expect(registry.selectGrammar().scopeName).toBe('text.plain.null-grammar')
|
||||
})
|
||||
|
||||
it('selects the text.plain grammar over the null grammar', async () => {
|
||||
await atom.packages.activatePackage('language-text')
|
||||
expect(atom.grammars.selectGrammar('test.txt').scopeName).toBe('text.plain')
|
||||
expect(atom.grammars.selectGrammar('test.txt').scopeName).toBe(
|
||||
'text.plain'
|
||||
)
|
||||
})
|
||||
|
||||
it('selects a grammar based on the file path case insensitively', async () => {
|
||||
await atom.packages.activatePackage('language-coffee-script')
|
||||
expect(atom.grammars.selectGrammar('/tmp/source.coffee').scopeName).toBe('source.coffee')
|
||||
expect(atom.grammars.selectGrammar('/tmp/source.COFFEE').scopeName).toBe('source.coffee')
|
||||
expect(atom.grammars.selectGrammar('/tmp/source.coffee').scopeName).toBe(
|
||||
'source.coffee'
|
||||
)
|
||||
expect(atom.grammars.selectGrammar('/tmp/source.COFFEE').scopeName).toBe(
|
||||
'source.coffee'
|
||||
)
|
||||
})
|
||||
|
||||
describe('on Windows', () => {
|
||||
@ -258,16 +338,18 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
originalPlatform = process.platform
|
||||
Object.defineProperty(process, 'platform', {value: 'win32'})
|
||||
Object.defineProperty(process, 'platform', { value: 'win32' })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', {value: originalPlatform})
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform })
|
||||
})
|
||||
|
||||
it('normalizes back slashes to forward slashes when matching the fileTypes', async () => {
|
||||
await atom.packages.activatePackage('language-git')
|
||||
expect(atom.grammars.selectGrammar('something\\.git\\config').scopeName).toBe('source.git-config')
|
||||
expect(
|
||||
atom.grammars.selectGrammar('something\\.git\\config').scopeName
|
||||
).toBe('source.git-config')
|
||||
})
|
||||
})
|
||||
|
||||
@ -277,10 +359,14 @@ describe('GrammarRegistry', () => {
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
|
||||
expect(atom.grammars.selectGrammar('file.js').name).toBe('JavaScript') // based on extension (.js)
|
||||
expect(atom.grammars.selectGrammar(path.join(temp.dir, '.git', 'config')).name).toBe('Git Config') // based on end of the path (.git/config)
|
||||
expect(
|
||||
atom.grammars.selectGrammar(path.join(temp.dir, '.git', 'config')).name
|
||||
).toBe('Git Config') // based on end of the path (.git/config)
|
||||
expect(atom.grammars.selectGrammar('Rakefile').name).toBe('Ruby') // based on the file's basename (Rakefile)
|
||||
expect(atom.grammars.selectGrammar('curb').name).toBe('Null Grammar')
|
||||
expect(atom.grammars.selectGrammar('/hu.git/config').name).toBe('Null Grammar')
|
||||
expect(atom.grammars.selectGrammar('/hu.git/config').name).toBe(
|
||||
'Null Grammar'
|
||||
)
|
||||
})
|
||||
|
||||
it("uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", async () => {
|
||||
@ -296,13 +382,20 @@ describe('GrammarRegistry', () => {
|
||||
await atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
let fileContent = 'first-line\n<html>'
|
||||
expect(atom.grammars.selectGrammar('dummy.coffee', fileContent).name).toBe('CoffeeScript')
|
||||
expect(
|
||||
atom.grammars.selectGrammar('dummy.coffee', fileContent).name
|
||||
).toBe('CoffeeScript')
|
||||
|
||||
fileContent = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
expect(atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name).toBe('Null Grammar')
|
||||
expect(
|
||||
atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name
|
||||
).toBe('Null Grammar')
|
||||
|
||||
fileContent += '\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
|
||||
expect(atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name).toBe('Property List (XML)')
|
||||
fileContent +=
|
||||
'\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
|
||||
expect(
|
||||
atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name
|
||||
).toBe('Property List (XML)')
|
||||
})
|
||||
|
||||
it("doesn't read the file when the file contents are specified", async () => {
|
||||
@ -311,28 +404,36 @@ describe('GrammarRegistry', () => {
|
||||
const filePath = require.resolve('./fixtures/shebang')
|
||||
const filePathContents = fs.readFileSync(filePath, 'utf8')
|
||||
spyOn(fs, 'read').andCallThrough()
|
||||
expect(atom.grammars.selectGrammar(filePath, filePathContents).name).toBe('Ruby')
|
||||
expect(atom.grammars.selectGrammar(filePath, filePathContents).name).toBe(
|
||||
'Ruby'
|
||||
)
|
||||
expect(fs.read).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('when multiple grammars have matching fileTypes', () => {
|
||||
it('selects the grammar with the longest fileType match', () => {
|
||||
const grammarPath1 = temp.path({suffix: '.json'})
|
||||
fs.writeFileSync(grammarPath1, JSON.stringify({
|
||||
name: 'test1',
|
||||
scopeName: 'source1',
|
||||
fileTypes: ['test']
|
||||
}))
|
||||
const grammarPath1 = temp.path({ suffix: '.json' })
|
||||
fs.writeFileSync(
|
||||
grammarPath1,
|
||||
JSON.stringify({
|
||||
name: 'test1',
|
||||
scopeName: 'source1',
|
||||
fileTypes: ['test']
|
||||
})
|
||||
)
|
||||
const grammar1 = atom.grammars.loadGrammarSync(grammarPath1)
|
||||
expect(atom.grammars.selectGrammar('more.test', '')).toBe(grammar1)
|
||||
fs.removeSync(grammarPath1)
|
||||
|
||||
const grammarPath2 = temp.path({suffix: '.json'})
|
||||
fs.writeFileSync(grammarPath2, JSON.stringify({
|
||||
name: 'test2',
|
||||
scopeName: 'source2',
|
||||
fileTypes: ['test', 'more.test']
|
||||
}))
|
||||
const grammarPath2 = temp.path({ suffix: '.json' })
|
||||
fs.writeFileSync(
|
||||
grammarPath2,
|
||||
JSON.stringify({
|
||||
name: 'test2',
|
||||
scopeName: 'source2',
|
||||
fileTypes: ['test', 'more.test']
|
||||
})
|
||||
)
|
||||
const grammar2 = atom.grammars.loadGrammarSync(grammarPath2)
|
||||
expect(atom.grammars.selectGrammar('more.test', '')).toBe(grammar2)
|
||||
return fs.removeSync(grammarPath2)
|
||||
@ -341,19 +442,28 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
it('favors non-bundled packages when breaking scoring ties', async () => {
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'packages', 'package-with-rb-filetype'))
|
||||
await atom.packages.activatePackage(
|
||||
path.join(__dirname, 'fixtures', 'packages', 'package-with-rb-filetype')
|
||||
)
|
||||
|
||||
atom.grammars.grammarForScopeName('source.ruby').bundledPackage = true
|
||||
atom.grammars.grammarForScopeName('test.rb').bundledPackage = false
|
||||
|
||||
expect(atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env ruby').scopeName).toBe('source.ruby')
|
||||
expect(atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env testruby').scopeName).toBe('test.rb')
|
||||
expect(
|
||||
atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env ruby').scopeName
|
||||
).toBe('source.ruby')
|
||||
expect(
|
||||
atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env testruby')
|
||||
.scopeName
|
||||
).toBe('test.rb')
|
||||
expect(atom.grammars.selectGrammar('test.rb').scopeName).toBe('test.rb')
|
||||
})
|
||||
|
||||
describe('when there is no file path', () => {
|
||||
it('does not throw an exception (regression)', () => {
|
||||
expect(() => atom.grammars.selectGrammar(null, '#!/usr/bin/ruby')).not.toThrow()
|
||||
expect(() =>
|
||||
atom.grammars.selectGrammar(null, '#!/usr/bin/ruby')
|
||||
).not.toThrow()
|
||||
expect(() => atom.grammars.selectGrammar(null, '')).not.toThrow()
|
||||
expect(() => atom.grammars.selectGrammar(null, null)).not.toThrow()
|
||||
})
|
||||
@ -362,8 +472,11 @@ describe('GrammarRegistry', () => {
|
||||
describe('when the user has custom grammar file types', () => {
|
||||
it('considers the custom file types as well as those defined in the grammar', async () => {
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
atom.config.set('core.customFileTypes', {'source.ruby': ['Cheffile']})
|
||||
expect(atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"').scopeName).toBe('source.ruby')
|
||||
atom.config.set('core.customFileTypes', { 'source.ruby': ['Cheffile'] })
|
||||
expect(
|
||||
atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"')
|
||||
.scopeName
|
||||
).toBe('source.ruby')
|
||||
})
|
||||
|
||||
it('favors user-defined file types over built-in ones of equal length', async () => {
|
||||
@ -374,31 +487,50 @@ describe('GrammarRegistry', () => {
|
||||
'source.coffee': ['Rakefile'],
|
||||
'source.ruby': ['Cakefile']
|
||||
})
|
||||
expect(atom.grammars.selectGrammar('Rakefile', '').scopeName).toBe('source.coffee')
|
||||
expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe('source.ruby')
|
||||
expect(atom.grammars.selectGrammar('Rakefile', '').scopeName).toBe(
|
||||
'source.coffee'
|
||||
)
|
||||
expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe(
|
||||
'source.ruby'
|
||||
)
|
||||
})
|
||||
|
||||
it('favors user-defined file types over grammars with matching first-line-regexps', async () => {
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
|
||||
atom.config.set('core.customFileTypes', {'source.ruby': ['bootstrap']})
|
||||
expect(atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node').scopeName).toBe('source.ruby')
|
||||
atom.config.set('core.customFileTypes', {
|
||||
'source.ruby': ['bootstrap']
|
||||
})
|
||||
expect(
|
||||
atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node')
|
||||
.scopeName
|
||||
).toBe('source.ruby')
|
||||
})
|
||||
})
|
||||
|
||||
it('favors a grammar with a matching file type over one with m matching first line pattern', async () => {
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
expect(atom.grammars.selectGrammar('foo.rb', '#!/usr/bin/env node').scopeName).toBe('source.ruby')
|
||||
expect(
|
||||
atom.grammars.selectGrammar('foo.rb', '#!/usr/bin/env node').scopeName
|
||||
).toBe('source.ruby')
|
||||
})
|
||||
|
||||
describe('tree-sitter vs text-mate', () => {
|
||||
it('favors a text-mate grammar over a tree-sitter grammar when `core.useTreeSitterParsers` is false', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', false, {scopeSelector: '.source.js'})
|
||||
atom.config.set('core.useTreeSitterParsers', false, {
|
||||
scopeSelector: '.source.js'
|
||||
})
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
|
||||
const grammar = grammarRegistry.selectGrammar('test.js')
|
||||
expect(grammar.scopeName).toBe('source.js')
|
||||
@ -408,8 +540,14 @@ describe('GrammarRegistry', () => {
|
||||
it('favors a tree-sitter grammar over a text-mate grammar when `core.useTreeSitterParsers` is true', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
|
||||
const grammar = grammarRegistry.selectGrammar('test.js')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBe(true)
|
||||
@ -417,7 +555,11 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
it('only favors a tree-sitter grammar if it actually matches in some way (regression)', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
|
||||
const grammar = grammarRegistry.selectGrammar('test', '')
|
||||
expect(grammar.name).toBe('Null Grammar')
|
||||
@ -427,110 +569,164 @@ describe('GrammarRegistry', () => {
|
||||
describe('tree-sitter grammars with content regexes', () => {
|
||||
it('recognizes C++ header files', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/tree-sitter-c.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/tree-sitter-cpp.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-coffee-script/grammars/coffeescript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/tree-sitter-c.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/tree-sitter-cpp.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-coffee-script/grammars/coffeescript.cson')
|
||||
)
|
||||
|
||||
let grammar = grammarRegistry.selectGrammar('test.h', dedent `
|
||||
let grammar = grammarRegistry.selectGrammar(
|
||||
'test.h',
|
||||
dedent`
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
void verb();
|
||||
} Noun;
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('C')
|
||||
|
||||
grammar = grammarRegistry.selectGrammar('test.h', dedent `
|
||||
grammar = grammarRegistry.selectGrammar(
|
||||
'test.h',
|
||||
dedent`
|
||||
#include <string>
|
||||
|
||||
class Noun {
|
||||
public:
|
||||
void verb();
|
||||
};
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('C++')
|
||||
|
||||
// The word `class` only indicates C++ in `.h` files, not in all files.
|
||||
grammar = grammarRegistry.selectGrammar('test.coffee', dedent `
|
||||
grammar = grammarRegistry.selectGrammar(
|
||||
'test.coffee',
|
||||
dedent`
|
||||
module.exports =
|
||||
class Noun
|
||||
verb: -> true
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('CoffeeScript')
|
||||
})
|
||||
|
||||
it('recognizes C++ files that do not match the content regex (regression)', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/tree-sitter-c.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/c++.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/tree-sitter-cpp.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/tree-sitter-c.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/c++.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/tree-sitter-cpp.cson')
|
||||
)
|
||||
|
||||
let grammar = grammarRegistry.selectGrammar('test.cc', dedent `
|
||||
let grammar = grammarRegistry.selectGrammar(
|
||||
'test.cc',
|
||||
dedent`
|
||||
int a();
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('C++')
|
||||
})
|
||||
|
||||
it('does not apply content regexes from grammars without filetype or first line matches', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/tree-sitter-cpp.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/tree-sitter-cpp.cson')
|
||||
)
|
||||
|
||||
let grammar = grammarRegistry.selectGrammar('', dedent `
|
||||
let grammar = grammarRegistry.selectGrammar(
|
||||
'',
|
||||
dedent`
|
||||
class Foo
|
||||
# this is ruby, not C++
|
||||
end
|
||||
`)
|
||||
`
|
||||
)
|
||||
|
||||
expect(grammar.name).toBe('Null Grammar')
|
||||
})
|
||||
|
||||
it('recognizes shell scripts with shebang lines', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-shellscript/grammars/shell-unix-bash.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-shellscript/grammars/tree-sitter-bash.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-shellscript/grammars/shell-unix-bash.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-shellscript/grammars/tree-sitter-bash.cson')
|
||||
)
|
||||
|
||||
let grammar = grammarRegistry.selectGrammar('test.h', dedent `
|
||||
let grammar = grammarRegistry.selectGrammar(
|
||||
'test.h',
|
||||
dedent`
|
||||
#!/bin/bash
|
||||
|
||||
echo "hi"
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('Shell Script')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBeTruthy()
|
||||
|
||||
grammar = grammarRegistry.selectGrammar('test.h', dedent `
|
||||
grammar = grammarRegistry.selectGrammar(
|
||||
'test.h',
|
||||
dedent`
|
||||
# vim: set ft=bash
|
||||
|
||||
echo "hi"
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('Shell Script')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBeTruthy()
|
||||
|
||||
atom.config.set('core.useTreeSitterParsers', false)
|
||||
grammar = grammarRegistry.selectGrammar('test.h', dedent `
|
||||
grammar = grammarRegistry.selectGrammar(
|
||||
'test.h',
|
||||
dedent`
|
||||
#!/bin/bash
|
||||
|
||||
echo "hi"
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('Shell Script')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBeFalsy()
|
||||
})
|
||||
|
||||
it('recognizes JavaScript files that use Flow', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-typescript/grammars/tree-sitter-flow.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve(
|
||||
'language-javascript/grammars/tree-sitter-javascript.cson'
|
||||
)
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-typescript/grammars/tree-sitter-flow.cson')
|
||||
)
|
||||
|
||||
let grammar = grammarRegistry.selectGrammar('test.js', dedent`
|
||||
let grammar = grammarRegistry.selectGrammar(
|
||||
'test.js',
|
||||
dedent`
|
||||
// Copyright something
|
||||
// @flow
|
||||
|
||||
module.exports = function () { return 1 + 1 }
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('Flow JavaScript')
|
||||
|
||||
grammar = grammarRegistry.selectGrammar('test.js', dedent`
|
||||
grammar = grammarRegistry.selectGrammar(
|
||||
'test.js',
|
||||
dedent`
|
||||
module.exports = function () { return 1 + 1 }
|
||||
`)
|
||||
`
|
||||
)
|
||||
expect(grammar.name).toBe('JavaScript')
|
||||
})
|
||||
})
|
||||
@ -548,8 +744,12 @@ describe('GrammarRegistry', () => {
|
||||
describe('.addInjectionPoint(languageId, {type, language, content})', () => {
|
||||
const injectionPoint = {
|
||||
type: 'some_node_type',
|
||||
language() { return 'some_language_name' },
|
||||
content(node) { return node }
|
||||
language () {
|
||||
return 'some_language_name'
|
||||
},
|
||||
content (node) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -574,13 +774,19 @@ describe('GrammarRegistry', () => {
|
||||
})
|
||||
|
||||
describe('serialization', () => {
|
||||
it('persists editors\' grammar overrides', async () => {
|
||||
it("persists editors' grammar overrides", async () => {
|
||||
const buffer1 = new TextBuffer()
|
||||
const buffer2 = new TextBuffer()
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-c/grammars/c.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-html/grammars/html.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/c.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-html/grammars/html.cson')
|
||||
)
|
||||
grammarRegistry.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
|
||||
grammarRegistry.maintainLanguageMode(buffer1)
|
||||
grammarRegistry.maintainLanguageMode(buffer2)
|
||||
@ -590,11 +796,17 @@ describe('GrammarRegistry', () => {
|
||||
const buffer1Copy = await TextBuffer.deserialize(buffer1.serialize())
|
||||
const buffer2Copy = await TextBuffer.deserialize(buffer2.serialize())
|
||||
|
||||
const grammarRegistryCopy = new GrammarRegistry({config: atom.config})
|
||||
grammarRegistryCopy.deserialize(JSON.parse(JSON.stringify(grammarRegistry.serialize())))
|
||||
const grammarRegistryCopy = new GrammarRegistry({ config: atom.config })
|
||||
grammarRegistryCopy.deserialize(
|
||||
JSON.parse(JSON.stringify(grammarRegistry.serialize()))
|
||||
)
|
||||
|
||||
grammarRegistryCopy.loadGrammarSync(require.resolve('language-c/grammars/c.cson'))
|
||||
grammarRegistryCopy.loadGrammarSync(require.resolve('language-html/grammars/html.cson'))
|
||||
grammarRegistryCopy.loadGrammarSync(
|
||||
require.resolve('language-c/grammars/c.cson')
|
||||
)
|
||||
grammarRegistryCopy.loadGrammarSync(
|
||||
require.resolve('language-html/grammars/html.cson')
|
||||
)
|
||||
|
||||
expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe(null)
|
||||
expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe(null)
|
||||
@ -604,7 +816,9 @@ describe('GrammarRegistry', () => {
|
||||
expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c')
|
||||
expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe(null)
|
||||
|
||||
grammarRegistryCopy.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistryCopy.loadGrammarSync(
|
||||
require.resolve('language-javascript/grammars/javascript.cson')
|
||||
)
|
||||
expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c')
|
||||
expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe('source.js')
|
||||
})
|
||||
|
@ -14,26 +14,28 @@ describe('GutterContainer', () => {
|
||||
describe('when initialized', () =>
|
||||
it('it has no gutters', () => {
|
||||
expect(gutterContainer.getGutters().length).toBe(0)
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe('::addGutter', () => {
|
||||
it('creates a new gutter', () => {
|
||||
const newGutter = gutterContainer.addGutter({'test-gutter': 'test-gutter', priority: 1})
|
||||
const newGutter = gutterContainer.addGutter({
|
||||
'test-gutter': 'test-gutter',
|
||||
priority: 1
|
||||
})
|
||||
expect(gutterContainer.getGutters()).toEqual([newGutter])
|
||||
expect(newGutter.priority).toBe(1)
|
||||
})
|
||||
|
||||
it('throws an error if the provided gutter name is already in use', () => {
|
||||
const name = 'test-gutter'
|
||||
gutterContainer.addGutter({name})
|
||||
expect(gutterContainer.addGutter.bind(null, {name})).toThrow()
|
||||
gutterContainer.addGutter({ name })
|
||||
expect(gutterContainer.addGutter.bind(null, { name })).toThrow()
|
||||
})
|
||||
|
||||
it('keeps added gutters sorted by ascending priority', () => {
|
||||
const gutter1 = gutterContainer.addGutter({name: 'first', priority: 1})
|
||||
const gutter3 = gutterContainer.addGutter({name: 'third', priority: 3})
|
||||
const gutter2 = gutterContainer.addGutter({name: 'second', priority: 2})
|
||||
const gutter1 = gutterContainer.addGutter({ name: 'first', priority: 1 })
|
||||
const gutter3 = gutterContainer.addGutter({ name: 'third', priority: 3 })
|
||||
const gutter2 = gutterContainer.addGutter({ name: 'second', priority: 2 })
|
||||
expect(gutterContainer.getGutters()).toEqual([gutter1, gutter2, gutter3])
|
||||
})
|
||||
})
|
||||
@ -44,11 +46,13 @@ describe('GutterContainer', () => {
|
||||
beforeEach(function () {
|
||||
gutterContainer = new GutterContainer(fakeTextEditor)
|
||||
removedGutters = []
|
||||
gutterContainer.onDidRemoveGutter(gutterName => removedGutters.push(gutterName))
|
||||
gutterContainer.onDidRemoveGutter(gutterName =>
|
||||
removedGutters.push(gutterName)
|
||||
)
|
||||
})
|
||||
|
||||
it('removes the gutter if it is contained by this GutterContainer', () => {
|
||||
const gutter = gutterContainer.addGutter({'test-gutter': 'test-gutter'})
|
||||
const gutter = gutterContainer.addGutter({ 'test-gutter': 'test-gutter' })
|
||||
expect(gutterContainer.getGutters()).toEqual([gutter])
|
||||
gutterContainer.removeGutter(gutter)
|
||||
expect(gutterContainer.getGutters().length).toBe(0)
|
||||
@ -65,13 +69,15 @@ describe('GutterContainer', () => {
|
||||
|
||||
describe('::destroy', () =>
|
||||
it('clears its array of gutters and destroys custom gutters', () => {
|
||||
const newGutter = gutterContainer.addGutter({'test-gutter': 'test-gutter', priority: 1})
|
||||
const newGutter = gutterContainer.addGutter({
|
||||
'test-gutter': 'test-gutter',
|
||||
priority: 1
|
||||
})
|
||||
const newGutterSpy = jasmine.createSpy()
|
||||
newGutter.onDidDestroy(newGutterSpy)
|
||||
|
||||
gutterContainer.destroy()
|
||||
expect(newGutterSpy).toHaveBeenCalled()
|
||||
expect(gutterContainer.getGutters()).toEqual([])
|
||||
})
|
||||
)
|
||||
}))
|
||||
})
|
||||
|
@ -24,8 +24,7 @@ describe('Gutter', () => {
|
||||
expect(gutter.isVisible()).toBe(false)
|
||||
// An event should only be emitted when the visibility changes.
|
||||
expect(events.length).toBe(1)
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe('::show', () =>
|
||||
it('shows the gutter if it is hidden.', () => {
|
||||
@ -45,8 +44,7 @@ describe('Gutter', () => {
|
||||
expect(gutter.isVisible()).toBe(true)
|
||||
// An event should only be emitted when the visibility changes.
|
||||
expect(events.length).toBe(1)
|
||||
})
|
||||
)
|
||||
}))
|
||||
|
||||
describe('::destroy', () => {
|
||||
let mockGutterContainer, mockGutterContainerRemovedGutters
|
||||
@ -61,21 +59,23 @@ describe('Gutter', () => {
|
||||
})
|
||||
|
||||
it('removes the gutter from its container.', () => {
|
||||
const gutter = new Gutter(mockGutterContainer, {name})
|
||||
const gutter = new Gutter(mockGutterContainer, { name })
|
||||
gutter.destroy()
|
||||
expect(mockGutterContainerRemovedGutters).toEqual([gutter])
|
||||
})
|
||||
|
||||
it('calls all callbacks registered on ::onDidDestroy.', () => {
|
||||
const gutter = new Gutter(mockGutterContainer, {name})
|
||||
const gutter = new Gutter(mockGutterContainer, { name })
|
||||
let didDestroy = false
|
||||
gutter.onDidDestroy(() => { didDestroy = true })
|
||||
gutter.onDidDestroy(() => {
|
||||
didDestroy = true
|
||||
})
|
||||
gutter.destroy()
|
||||
expect(didDestroy).toBe(true)
|
||||
})
|
||||
|
||||
it('does not allow destroying the line-number gutter', () => {
|
||||
const gutter = new Gutter(mockGutterContainer, {name: 'line-number'})
|
||||
const gutter = new Gutter(mockGutterContainer, { name: 'line-number' })
|
||||
expect(gutter.destroy).toThrow()
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
const WORDS = require('./words')
|
||||
const {Point, Range} = require('text-buffer')
|
||||
const { Point, Range } = require('text-buffer')
|
||||
|
||||
exports.getRandomBufferRange = function getRandomBufferRange (random, buffer) {
|
||||
const endRow = random(buffer.getLineCount())
|
||||
|
@ -1,10 +1,7 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
const {Emitter, Disposable, CompositeDisposable} = require('event-kit')
|
||||
|
||||
const {HistoryManager, HistoryProject} = require('../src/history-manager')
|
||||
const { HistoryManager, HistoryProject } = require('../src/history-manager')
|
||||
const StateStore = require('../src/state-store')
|
||||
|
||||
describe("HistoryManager", () => {
|
||||
describe('HistoryManager', () => {
|
||||
let historyManager, commandRegistry, project, stateStore
|
||||
let commandDisposable, projectDisposable
|
||||
|
||||
@ -16,19 +13,26 @@ describe("HistoryManager", () => {
|
||||
stateStore = new StateStore('history-manager-test', 1)
|
||||
await stateStore.save('history-manager', {
|
||||
projects: [
|
||||
{paths: ['/1', 'c:\\2'], lastOpened: new Date(2016, 9, 17, 17, 16, 23)},
|
||||
{paths: ['/test'], lastOpened: new Date(2016, 9, 17, 11, 12, 13)}
|
||||
{
|
||||
paths: ['/1', 'c:\\2'],
|
||||
lastOpened: new Date(2016, 9, 17, 17, 16, 23)
|
||||
},
|
||||
{ paths: ['/test'], lastOpened: new Date(2016, 9, 17, 11, 12, 13) }
|
||||
]
|
||||
})
|
||||
|
||||
projectDisposable = jasmine.createSpyObj('Disposable', ['dispose'])
|
||||
project = jasmine.createSpyObj('Project', ['onDidChangePaths'])
|
||||
project.onDidChangePaths.andCallFake((f) => {
|
||||
project.onDidChangePaths.andCallFake(f => {
|
||||
project.didChangePathsListener = f
|
||||
return projectDisposable
|
||||
})
|
||||
|
||||
historyManager = new HistoryManager({stateStore, project, commands: commandRegistry})
|
||||
historyManager = new HistoryManager({
|
||||
stateStore,
|
||||
project,
|
||||
commands: commandRegistry
|
||||
})
|
||||
await historyManager.loadState()
|
||||
})
|
||||
|
||||
@ -36,24 +40,29 @@ describe("HistoryManager", () => {
|
||||
await stateStore.clear()
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
describe('constructor', () => {
|
||||
it("registers the 'clear-project-history' command function", () => {
|
||||
expect(commandRegistry.add).toHaveBeenCalled()
|
||||
const cmdCall = commandRegistry.add.calls[0]
|
||||
expect(cmdCall.args.length).toBe(3)
|
||||
expect(cmdCall.args[0]).toBe('atom-workspace')
|
||||
expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe('function')
|
||||
expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe(
|
||||
'function'
|
||||
)
|
||||
})
|
||||
|
||||
describe("getProjects", () => {
|
||||
it("returns an array of HistoryProjects", () => {
|
||||
describe('getProjects', () => {
|
||||
it('returns an array of HistoryProjects', () => {
|
||||
expect(historyManager.getProjects()).toEqual([
|
||||
new HistoryProject(['/1', 'c:\\2'], new Date(2016, 9, 17, 17, 16, 23)),
|
||||
new HistoryProject(
|
||||
['/1', 'c:\\2'],
|
||||
new Date(2016, 9, 17, 17, 16, 23)
|
||||
),
|
||||
new HistoryProject(['/test'], new Date(2016, 9, 17, 11, 12, 13))
|
||||
])
|
||||
})
|
||||
|
||||
it("returns an array of HistoryProjects that is not mutable state", () => {
|
||||
it('returns an array of HistoryProjects that is not mutable state', () => {
|
||||
const firstProjects = historyManager.getProjects()
|
||||
firstProjects.pop()
|
||||
firstProjects[0].path = 'modified'
|
||||
@ -64,21 +73,25 @@ describe("HistoryManager", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("clearProjects", () => {
|
||||
it("clears the list of projects", async () => {
|
||||
describe('clearProjects', () => {
|
||||
it('clears the list of projects', async () => {
|
||||
expect(historyManager.getProjects().length).not.toBe(0)
|
||||
await historyManager.clearProjects()
|
||||
expect(historyManager.getProjects().length).toBe(0)
|
||||
})
|
||||
|
||||
it("saves the state", async () => {
|
||||
it('saves the state', async () => {
|
||||
await historyManager.clearProjects()
|
||||
const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry})
|
||||
const historyManager2 = new HistoryManager({
|
||||
stateStore,
|
||||
project,
|
||||
commands: commandRegistry
|
||||
})
|
||||
await historyManager2.loadState()
|
||||
expect(historyManager.getProjects().length).toBe(0)
|
||||
})
|
||||
|
||||
it("fires the onDidChangeProjects event", async () => {
|
||||
it('fires the onDidChangeProjects event', async () => {
|
||||
const didChangeSpy = jasmine.createSpy()
|
||||
historyManager.onDidChangeProjects(didChangeSpy)
|
||||
await historyManager.clearProjects()
|
||||
@ -87,7 +100,7 @@ describe("HistoryManager", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("listens to project.onDidChangePaths adding a new project", () => {
|
||||
it('listens to project.onDidChangePaths adding a new project', () => {
|
||||
const start = new Date()
|
||||
project.didChangePathsListener(['/a/new', '/path/or/two'])
|
||||
const projects = historyManager.getProjects()
|
||||
@ -96,7 +109,7 @@ describe("HistoryManager", () => {
|
||||
expect(projects[0].lastOpened).not.toBeLessThan(start)
|
||||
})
|
||||
|
||||
it("listens to project.onDidChangePaths updating an existing project", () => {
|
||||
it('listens to project.onDidChangePaths updating an existing project', () => {
|
||||
const start = new Date()
|
||||
project.didChangePathsListener(['/test'])
|
||||
const projects = historyManager.getProjects()
|
||||
@ -106,22 +119,22 @@ describe("HistoryManager", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("loadState", () => {
|
||||
it("defaults to an empty array if no state", async () => {
|
||||
describe('loadState', () => {
|
||||
it('defaults to an empty array if no state', async () => {
|
||||
await stateStore.clear()
|
||||
await historyManager.loadState()
|
||||
expect(historyManager.getProjects()).toEqual([])
|
||||
})
|
||||
|
||||
it("defaults to an empty array if no projects", async () => {
|
||||
it('defaults to an empty array if no projects', async () => {
|
||||
await stateStore.save('history-manager', {})
|
||||
await historyManager.loadState()
|
||||
expect(historyManager.getProjects()).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("addProject", () => {
|
||||
it("adds a new project to the end", async () => {
|
||||
describe('addProject', () => {
|
||||
it('adds a new project to the end', async () => {
|
||||
const date = new Date(2010, 10, 9, 8, 7, 6)
|
||||
await historyManager.addProject(['/a/b'], date)
|
||||
const projects = historyManager.getProjects()
|
||||
@ -130,7 +143,7 @@ describe("HistoryManager", () => {
|
||||
expect(projects[2].lastOpened).toBe(date)
|
||||
})
|
||||
|
||||
it("adds a new project to the start", async () => {
|
||||
it('adds a new project to the start', async () => {
|
||||
const date = new Date()
|
||||
await historyManager.addProject(['/so/new'], date)
|
||||
const projects = historyManager.getProjects()
|
||||
@ -139,7 +152,7 @@ describe("HistoryManager", () => {
|
||||
expect(projects[0].lastOpened).toBe(date)
|
||||
})
|
||||
|
||||
it("updates an existing project and moves it to the start", async () => {
|
||||
it('updates an existing project and moves it to the start', async () => {
|
||||
const date = new Date()
|
||||
await historyManager.addProject(['/test'], date)
|
||||
const projects = historyManager.getProjects()
|
||||
@ -148,7 +161,7 @@ describe("HistoryManager", () => {
|
||||
expect(projects[0].lastOpened).toBe(date)
|
||||
})
|
||||
|
||||
it("fires the onDidChangeProjects event when adding a project", async () => {
|
||||
it('fires the onDidChangeProjects event when adding a project', async () => {
|
||||
const didChangeSpy = jasmine.createSpy()
|
||||
const beforeCount = historyManager.getProjects().length
|
||||
historyManager.onDidChangeProjects(didChangeSpy)
|
||||
@ -157,7 +170,7 @@ describe("HistoryManager", () => {
|
||||
expect(historyManager.getProjects().length).toBe(beforeCount + 1)
|
||||
})
|
||||
|
||||
it("fires the onDidChangeProjects event when updating a project", async () => {
|
||||
it('fires the onDidChangeProjects event when updating a project', async () => {
|
||||
const didChangeSpy = jasmine.createSpy()
|
||||
const beforeCount = historyManager.getProjects().length
|
||||
historyManager.onDidChangeProjects(didChangeSpy)
|
||||
@ -167,8 +180,8 @@ describe("HistoryManager", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("getProject", () => {
|
||||
it("returns a project that matches the paths", () => {
|
||||
describe('getProject', () => {
|
||||
it('returns a project that matches the paths', () => {
|
||||
const project = historyManager.getProject(['/1', 'c:\\2'])
|
||||
expect(project).not.toBeNull()
|
||||
expect(project.paths).toEqual(['/1', 'c:\\2'])
|
||||
@ -180,7 +193,7 @@ describe("HistoryManager", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("saveState", () => {
|
||||
describe('saveState', () => {
|
||||
let savedHistory
|
||||
beforeEach(() => {
|
||||
// historyManager.saveState is spied on globally to prevent specs from
|
||||
@ -195,11 +208,17 @@ describe("HistoryManager", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("saves the state", async () => {
|
||||
await historyManager.addProject(["/save/state"])
|
||||
it('saves the state', async () => {
|
||||
await historyManager.addProject(['/save/state'])
|
||||
await historyManager.saveState()
|
||||
const historyManager2 = new HistoryManager({stateStore, project, commands: commandRegistry})
|
||||
spyOn(historyManager2.stateStore, 'load').andCallFake(name => Promise.resolve(savedHistory))
|
||||
const historyManager2 = new HistoryManager({
|
||||
stateStore,
|
||||
project,
|
||||
commands: commandRegistry
|
||||
})
|
||||
spyOn(historyManager2.stateStore, 'load').andCallFake(name =>
|
||||
Promise.resolve(savedHistory)
|
||||
)
|
||||
await historyManager2.loadState()
|
||||
expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state'])
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {TerminalReporter} = require('jasmine-tagged')
|
||||
const { TerminalReporter } = require('jasmine-tagged')
|
||||
|
||||
class JasmineListReporter extends TerminalReporter {
|
||||
fullDescription (spec) {
|
||||
|
@ -10,6 +10,16 @@ module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
|
||||
window[key] = value for key, value of require '../vendor/jasmine'
|
||||
require 'jasmine-tagged'
|
||||
|
||||
# Rewrite global jasmine functions to have support for async tests.
|
||||
# This way packages can create async specs without having to import these from the
|
||||
# async-spec-helpers file.
|
||||
global.it = asyncifyJasmineFn global.it, 1
|
||||
global.fit = asyncifyJasmineFn global.fit, 1
|
||||
global.ffit = asyncifyJasmineFn global.ffit, 1
|
||||
global.fffit = asyncifyJasmineFn global.fffit, 1
|
||||
global.beforeEach = asyncifyJasmineFn global.beforeEach, 0
|
||||
global.afterEach = asyncifyJasmineFn global.afterEach, 0
|
||||
|
||||
# Allow document.title to be assigned in specs without screwing up spec window title
|
||||
documentTitle = null
|
||||
Object.defineProperty document, 'title',
|
||||
@ -59,6 +69,28 @@ module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
|
||||
jasmineEnv.execute()
|
||||
promise
|
||||
|
||||
asyncifyJasmineFn = (fn, callbackPosition) ->
|
||||
(args...) ->
|
||||
if typeof args[callbackPosition] is 'function'
|
||||
callback = args[callbackPosition]
|
||||
|
||||
args[callbackPosition] = (args...) ->
|
||||
result = callback.apply this, args
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
fn.apply this, args
|
||||
|
||||
waitsForPromise = (fn) ->
|
||||
promise = fn()
|
||||
|
||||
global.waitsFor('spec promise to resolve', (done) ->
|
||||
promise.then(done, (error) ->
|
||||
jasmine.getEnv().currentSpec.fail error
|
||||
done()
|
||||
)
|
||||
)
|
||||
|
||||
disableFocusMethods = ->
|
||||
['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach (methodName) ->
|
||||
focusMethod = window[methodName]
|
||||
@ -124,4 +156,4 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
|
||||
new JasmineListReporter(options)
|
||||
else
|
||||
{TerminalReporter} = require 'jasmine-tagged'
|
||||
new TerminalReporter(options)
|
||||
new TerminalReporter(options)
|
@ -9,14 +9,21 @@ const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
const AtomApplication = require('../../src/main-process/atom-application')
|
||||
const parseCommandLine = require('../../src/main-process/parse-command-line')
|
||||
const {timeoutPromise, conditionPromise, emitterEventPromise} = require('../async-spec-helpers')
|
||||
const {
|
||||
timeoutPromise,
|
||||
conditionPromise,
|
||||
emitterEventPromise
|
||||
} = require('../async-spec-helpers')
|
||||
|
||||
const ATOM_RESOURCE_PATH = path.resolve(__dirname, '..', '..')
|
||||
|
||||
describe('AtomApplication', function () {
|
||||
this.timeout(60 * 1000)
|
||||
|
||||
let originalAppQuit, originalShowMessageBox, originalAtomHome, atomApplicationsToDestroy
|
||||
let originalAppQuit,
|
||||
originalShowMessageBox,
|
||||
originalAtomHome,
|
||||
atomApplicationsToDestroy
|
||||
|
||||
beforeEach(() => {
|
||||
originalAppQuit = electron.app.quit
|
||||
@ -25,11 +32,15 @@ describe('AtomApplication', function () {
|
||||
originalAtomHome = process.env.ATOM_HOME
|
||||
process.env.ATOM_HOME = makeTempDir('atom-home')
|
||||
// Symlinking the compile cache into the temporary home dir makes the windows load much faster
|
||||
fs.symlinkSync(path.join(originalAtomHome, 'compile-cache'), path.join(process.env.ATOM_HOME, 'compile-cache'), 'junction')
|
||||
fs.symlinkSync(
|
||||
path.join(originalAtomHome, 'compile-cache'),
|
||||
path.join(process.env.ATOM_HOME, 'compile-cache'),
|
||||
'junction'
|
||||
)
|
||||
season.writeFileSync(path.join(process.env.ATOM_HOME, 'config.cson'), {
|
||||
'*': {
|
||||
welcome: {showOnStartup: false},
|
||||
core: {telemetryConsent: 'no'}
|
||||
welcome: { showOnStartup: false },
|
||||
core: { telemetryConsent: 'no' }
|
||||
}
|
||||
})
|
||||
atomApplicationsToDestroy = []
|
||||
@ -54,10 +65,14 @@ describe('AtomApplication', function () {
|
||||
const tempDirPath2 = makeTempDir()
|
||||
|
||||
const atomApplication1 = buildAtomApplication()
|
||||
const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1]))
|
||||
const [app1Window1] = await atomApplication1.launch(
|
||||
parseCommandLine([tempDirPath1])
|
||||
)
|
||||
await emitterEventPromise(app1Window1, 'window:locations-opened')
|
||||
|
||||
const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2]))
|
||||
const [app1Window2] = await atomApplication1.launch(
|
||||
parseCommandLine([tempDirPath2])
|
||||
)
|
||||
await emitterEventPromise(app1Window2, 'window:locations-opened')
|
||||
|
||||
await Promise.all([
|
||||
@ -66,30 +81,46 @@ describe('AtomApplication', function () {
|
||||
])
|
||||
|
||||
const atomApplication2 = buildAtomApplication()
|
||||
const [app2Window1, app2Window2] = await atomApplication2.launch(parseCommandLine([]))
|
||||
const [app2Window1, app2Window2] = await atomApplication2.launch(
|
||||
parseCommandLine([])
|
||||
)
|
||||
await Promise.all([
|
||||
emitterEventPromise(app2Window1, 'window:locations-opened'),
|
||||
emitterEventPromise(app2Window2, 'window:locations-opened')
|
||||
])
|
||||
|
||||
assert.deepEqual(await getTreeViewRootDirectories(app2Window1), [tempDirPath1])
|
||||
assert.deepEqual(await getTreeViewRootDirectories(app2Window2), [tempDirPath2])
|
||||
assert.deepEqual(await getTreeViewRootDirectories(app2Window1), [
|
||||
tempDirPath1
|
||||
])
|
||||
assert.deepEqual(await getTreeViewRootDirectories(app2Window2), [
|
||||
tempDirPath2
|
||||
])
|
||||
})
|
||||
|
||||
it('when windows already exist, opens a new window with a single untitled buffer', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([]))
|
||||
await focusWindow(window1)
|
||||
const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
|
||||
})
|
||||
const window1EditorTitle = await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
sendBackToMainProcess(
|
||||
atom.workspace.getActiveTextEditor().getTitle()
|
||||
)
|
||||
}
|
||||
)
|
||||
assert.equal(window1EditorTitle, 'untitled')
|
||||
|
||||
const window2 = atomApplication.openWithOptions(parseCommandLine([]))
|
||||
await window2.loadedPromise
|
||||
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
|
||||
})
|
||||
const window2EditorTitle = await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
sendBackToMainProcess(
|
||||
atom.workspace.getActiveTextEditor().getTitle()
|
||||
)
|
||||
}
|
||||
)
|
||||
assert.equal(window2EditorTitle, 'untitled')
|
||||
|
||||
assert.deepEqual(atomApplication.getAllWindows(), [window2, window1])
|
||||
@ -101,10 +132,14 @@ describe('AtomApplication', function () {
|
||||
const tempDirPath2 = makeTempDir()
|
||||
|
||||
const atomApplication1 = buildAtomApplication()
|
||||
const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1]))
|
||||
const [app1Window1] = await atomApplication1.launch(
|
||||
parseCommandLine([tempDirPath1])
|
||||
)
|
||||
await emitterEventPromise(app1Window1, 'window:locations-opened')
|
||||
|
||||
const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2]))
|
||||
const [app1Window2] = await atomApplication1.launch(
|
||||
parseCommandLine([tempDirPath2])
|
||||
)
|
||||
await emitterEventPromise(app1Window2, 'window:locations-opened')
|
||||
|
||||
await Promise.all([
|
||||
@ -114,13 +149,20 @@ describe('AtomApplication', function () {
|
||||
|
||||
// Launch with --new-window
|
||||
const atomApplication2 = buildAtomApplication()
|
||||
const appWindows2 = await atomApplication2.launch(parseCommandLine(['--new-window']))
|
||||
const appWindows2 = await atomApplication2.launch(
|
||||
parseCommandLine(['--new-window'])
|
||||
)
|
||||
assert.lengthOf(appWindows2, 1)
|
||||
const [appWindow2] = appWindows2
|
||||
await appWindow2.loadedPromise
|
||||
const window2EditorTitle = await evalInWebContents(appWindow2.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
|
||||
})
|
||||
const window2EditorTitle = await evalInWebContents(
|
||||
appWindow2.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
sendBackToMainProcess(
|
||||
atom.workspace.getActiveTextEditor().getTitle()
|
||||
)
|
||||
}
|
||||
)
|
||||
assert.equal(window2EditorTitle, 'untitled')
|
||||
})
|
||||
|
||||
@ -135,12 +177,17 @@ describe('AtomApplication', function () {
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([]))
|
||||
await focusWindow(window1)
|
||||
|
||||
// wait a bit just to make sure we don't pass due to querying the render process before it loads
|
||||
// wait a bit just to make sure we don't pass due to querying the render process before it loads
|
||||
await timeoutPromise(1000)
|
||||
|
||||
const itemCount = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActivePane().getItems().length)
|
||||
})
|
||||
const itemCount = await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
sendBackToMainProcess(
|
||||
atom.workspace.getActivePane().getItems().length
|
||||
)
|
||||
}
|
||||
)
|
||||
assert.equal(itemCount, 0)
|
||||
})
|
||||
})
|
||||
@ -153,11 +200,18 @@ describe('AtomApplication', function () {
|
||||
fs.mkdirSync(dirBSubdirPath)
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath]))
|
||||
const [window1] = await atomApplication.launch(
|
||||
parseCommandLine([dirAPath, dirBPath])
|
||||
)
|
||||
await focusWindow(window1)
|
||||
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 2)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath])
|
||||
await conditionPromise(
|
||||
async () => (await getTreeViewRootDirectories(window1)).length === 2
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [
|
||||
dirAPath,
|
||||
dirBPath
|
||||
])
|
||||
})
|
||||
|
||||
it('can open to a specific line number of a file', async () => {
|
||||
@ -165,14 +219,19 @@ describe('AtomApplication', function () {
|
||||
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
|
||||
const atomApplication = buildAtomApplication()
|
||||
|
||||
const [window] = await atomApplication.launch(parseCommandLine([filePath + ':3']))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine([filePath + ':3'])
|
||||
)
|
||||
await focusWindow(window)
|
||||
|
||||
const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getCursorBufferPosition().row)
|
||||
})
|
||||
})
|
||||
const cursorRow = await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getCursorBufferPosition().row)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(cursorRow, 2)
|
||||
})
|
||||
@ -182,16 +241,21 @@ describe('AtomApplication', function () {
|
||||
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
|
||||
const atomApplication = buildAtomApplication()
|
||||
|
||||
const [window] = await atomApplication.launch(parseCommandLine([filePath + ':2:2']))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine([filePath + ':2:2'])
|
||||
)
|
||||
await focusWindow(window)
|
||||
|
||||
const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getCursorBufferPosition())
|
||||
})
|
||||
})
|
||||
const cursorPosition = await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getCursorBufferPosition())
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
assert.deepEqual(cursorPosition, {row: 1, column: 1})
|
||||
assert.deepEqual(cursorPosition, { row: 1, column: 1 })
|
||||
})
|
||||
|
||||
it('removes all trailing whitespace and colons from the specified path', async () => {
|
||||
@ -199,31 +263,44 @@ describe('AtomApplication', function () {
|
||||
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
|
||||
const atomApplication = buildAtomApplication()
|
||||
|
||||
const [window] = await atomApplication.launch(parseCommandLine([filePath + ':: ']))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine([filePath + ':: '])
|
||||
)
|
||||
await focusWindow(window)
|
||||
|
||||
const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
})
|
||||
})
|
||||
const openedPath = await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
assert.equal(openedPath, filePath)
|
||||
})
|
||||
|
||||
it('opens an empty text editor when launched with a new file path', async () => {
|
||||
// Choosing "Don't save"
|
||||
mockElectronShowMessageBox({response: 2})
|
||||
mockElectronShowMessageBox({ response: 2 })
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
const newFilePath = path.join(makeTempDir(), 'new-file')
|
||||
const [window] = await atomApplication.launch(parseCommandLine([newFilePath]))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine([newFilePath])
|
||||
)
|
||||
await focusWindow(window)
|
||||
const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(editor => {
|
||||
sendBackToMainProcess({editorTitle: editor.getTitle(), editorText: editor.getText()})
|
||||
})
|
||||
})
|
||||
const { editorTitle, editorText } = await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(editor => {
|
||||
sendBackToMainProcess({
|
||||
editorTitle: editor.getTitle(),
|
||||
editorText: editor.getText()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
assert.equal(editorTitle, path.basename(newFilePath))
|
||||
assert.equal(editorText, '')
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window), [])
|
||||
@ -239,32 +316,51 @@ describe('AtomApplication', function () {
|
||||
fs.writeFileSync(existingDirCFilePath, 'this is an existing file')
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([dirAPath]))
|
||||
const [window1] = await atomApplication.launch(
|
||||
parseCommandLine([dirAPath])
|
||||
)
|
||||
await focusWindow(window1)
|
||||
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 1)
|
||||
await conditionPromise(
|
||||
async () => (await getTreeViewRootDirectories(window1)).length === 1
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath])
|
||||
|
||||
// When opening *files* with --add, reuses an existing window
|
||||
let [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add']))
|
||||
let [reusedWindow] = await atomApplication.launch(
|
||||
parseCommandLine([existingDirCFilePath, '--add'])
|
||||
)
|
||||
assert.equal(reusedWindow, window1)
|
||||
assert.deepEqual(atomApplication.getAllWindows(), [window1])
|
||||
let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
subscription.dispose()
|
||||
})
|
||||
})
|
||||
let activeEditorPath = await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(
|
||||
textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
subscription.dispose()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
assert.equal(activeEditorPath, existingDirCFilePath)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath])
|
||||
|
||||
// When opening *directories* with --add, reuses an existing window and adds the directory to the project
|
||||
reusedWindow = (await atomApplication.launch(parseCommandLine([dirBPath, '-a'])))[0]
|
||||
reusedWindow = (await atomApplication.launch(
|
||||
parseCommandLine([dirBPath, '-a'])
|
||||
))[0]
|
||||
assert.equal(reusedWindow, window1)
|
||||
assert.deepEqual(atomApplication.getAllWindows(), [window1])
|
||||
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length === 2)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath])
|
||||
await conditionPromise(
|
||||
async () =>
|
||||
(await getTreeViewRootDirectories(reusedWindow)).length === 2
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [
|
||||
dirAPath,
|
||||
dirBPath
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -272,11 +368,15 @@ describe('AtomApplication', function () {
|
||||
it('positions new windows at an offset distance from the previous window', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([makeTempDir()]))
|
||||
const [window1] = await atomApplication.launch(
|
||||
parseCommandLine([makeTempDir()])
|
||||
)
|
||||
await focusWindow(window1)
|
||||
window1.browserWindow.setBounds({width: 400, height: 400, x: 0, y: 0})
|
||||
window1.browserWindow.setBounds({ width: 400, height: 400, x: 0, y: 0 })
|
||||
|
||||
const [window2] = await atomApplication.launch(parseCommandLine([makeTempDir()]))
|
||||
const [window2] = await atomApplication.launch(
|
||||
parseCommandLine([makeTempDir()])
|
||||
)
|
||||
await focusWindow(window2)
|
||||
|
||||
assert.notEqual(window1, window2)
|
||||
@ -289,70 +389,109 @@ describe('AtomApplication', function () {
|
||||
|
||||
it('persists window state based on the project directories', async () => {
|
||||
// Choosing "Don't save"
|
||||
mockElectronShowMessageBox({response: 2})
|
||||
mockElectronShowMessageBox({ response: 2 })
|
||||
|
||||
const tempDirPath = makeTempDir()
|
||||
const atomApplication = buildAtomApplication()
|
||||
const nonExistentFilePath = path.join(tempDirPath, 'new-file')
|
||||
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([tempDirPath, nonExistentFilePath]))
|
||||
await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
textEditor.insertText('Hello World!')
|
||||
sendBackToMainProcess(null)
|
||||
})
|
||||
})
|
||||
const [window1] = await atomApplication.launch(
|
||||
parseCommandLine([tempDirPath, nonExistentFilePath])
|
||||
)
|
||||
await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
textEditor.insertText('Hello World!')
|
||||
sendBackToMainProcess(null)
|
||||
})
|
||||
}
|
||||
)
|
||||
await window1.prepareToUnload()
|
||||
window1.close()
|
||||
await window1.closedPromise
|
||||
|
||||
// Restore unsaved state when opening the same project directory
|
||||
const [window2] = await atomApplication.launch(parseCommandLine([tempDirPath]))
|
||||
const [window2] = await atomApplication.launch(
|
||||
parseCommandLine([tempDirPath])
|
||||
)
|
||||
await window2.loadedPromise
|
||||
const window2Text = await evalInWebContents(window2.browserWindow.webContents, sendBackToMainProcess => {
|
||||
const textEditor = atom.workspace.getActiveTextEditor()
|
||||
textEditor.moveToBottom()
|
||||
textEditor.insertText(' How are you?')
|
||||
sendBackToMainProcess(textEditor.getText())
|
||||
})
|
||||
const window2Text = await evalInWebContents(
|
||||
window2.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
const textEditor = atom.workspace.getActiveTextEditor()
|
||||
textEditor.moveToBottom()
|
||||
textEditor.insertText(' How are you?')
|
||||
sendBackToMainProcess(textEditor.getText())
|
||||
}
|
||||
)
|
||||
assert.equal(window2Text, 'Hello World! How are you?')
|
||||
})
|
||||
|
||||
it('adds a remote directory to the project when launched with a remote directory', async () => {
|
||||
const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-directory-provider')
|
||||
const packagePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'fixtures',
|
||||
'packages',
|
||||
'package-with-directory-provider'
|
||||
)
|
||||
const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages')
|
||||
fs.mkdirSync(packagesDirPath)
|
||||
fs.symlinkSync(packagePath, path.join(packagesDirPath, 'package-with-directory-provider'), 'junction')
|
||||
fs.symlinkSync(
|
||||
packagePath,
|
||||
path.join(packagesDirPath, 'package-with-directory-provider'),
|
||||
'junction'
|
||||
)
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
atomApplication.config.set('core.disabledPackages', ['fuzzy-finder'])
|
||||
|
||||
const remotePath = 'remote://server:3437/some/directory/path'
|
||||
let [window] = await atomApplication.launch(parseCommandLine([remotePath]))
|
||||
let [window] = await atomApplication.launch(
|
||||
parseCommandLine([remotePath])
|
||||
)
|
||||
|
||||
await focusWindow(window)
|
||||
await conditionPromise(async () => (await getProjectDirectories()).length > 0)
|
||||
await conditionPromise(
|
||||
async () => (await getProjectDirectories()).length > 0
|
||||
)
|
||||
let directories = await getProjectDirectories()
|
||||
assert.deepEqual(directories, [{type: 'FakeRemoteDirectory', path: remotePath}])
|
||||
assert.deepEqual(directories, [
|
||||
{ type: 'FakeRemoteDirectory', path: remotePath }
|
||||
])
|
||||
|
||||
await window.reload()
|
||||
await focusWindow(window)
|
||||
directories = await getProjectDirectories()
|
||||
assert.deepEqual(directories, [{type: 'FakeRemoteDirectory', path: remotePath}])
|
||||
assert.deepEqual(directories, [
|
||||
{ type: 'FakeRemoteDirectory', path: remotePath }
|
||||
])
|
||||
|
||||
function getProjectDirectories () {
|
||||
return evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.project.getDirectories().map(d => ({ type: d.constructor.name, path: d.getPath() })))
|
||||
})
|
||||
return evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
sendBackToMainProcess(
|
||||
atom.project
|
||||
.getDirectories()
|
||||
.map(d => ({ type: d.constructor.name, path: d.getPath() }))
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => {
|
||||
const atomApplication1 = buildAtomApplication()
|
||||
const [app1Window1] = await atomApplication1.launch(parseCommandLine([makeTempDir()]))
|
||||
const [app1Window1] = await atomApplication1.launch(
|
||||
parseCommandLine([makeTempDir()])
|
||||
)
|
||||
await focusWindow(app1Window1)
|
||||
|
||||
const [app1Window2] = await atomApplication1.launch(parseCommandLine([makeTempDir()]))
|
||||
const [app1Window2] = await atomApplication1.launch(
|
||||
parseCommandLine([makeTempDir()])
|
||||
)
|
||||
await focusWindow(app1Window2)
|
||||
|
||||
const configPath = path.join(process.env.ATOM_HOME, 'config.cson')
|
||||
@ -382,19 +521,27 @@ describe('AtomApplication', function () {
|
||||
})
|
||||
|
||||
it('kills the specified pid after a newly-opened window is closed', async () => {
|
||||
const [window1] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101']))
|
||||
const [window1] = await atomApplication.launch(
|
||||
parseCommandLine(['--wait', '--pid', '101'])
|
||||
)
|
||||
await focusWindow(window1)
|
||||
|
||||
const [window2] = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102']))
|
||||
const [window2] = await atomApplication.launch(
|
||||
parseCommandLine(['--new-window', '--wait', '--pid', '102'])
|
||||
)
|
||||
await focusWindow(window2)
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
let processKillPromise = new Promise(resolve => {
|
||||
onDidKillProcess = resolve
|
||||
})
|
||||
window1.close()
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [101])
|
||||
|
||||
processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
processKillPromise = new Promise(resolve => {
|
||||
onDidKillProcess = resolve
|
||||
})
|
||||
window2.close()
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [101, 102])
|
||||
@ -407,18 +554,34 @@ describe('AtomApplication', function () {
|
||||
fs.writeFileSync(filePath1, 'File 1')
|
||||
fs.writeFileSync(filePath2, 'File 2')
|
||||
|
||||
const [window] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', projectDir]))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine(['--wait', '--pid', '101', projectDir])
|
||||
)
|
||||
await focusWindow(window)
|
||||
|
||||
const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--add', '--wait', '--pid', '102', filePath1, filePath2]))
|
||||
const [reusedWindow] = await atomApplication.launch(
|
||||
parseCommandLine([
|
||||
'--add',
|
||||
'--wait',
|
||||
'--pid',
|
||||
'102',
|
||||
filePath1,
|
||||
filePath2
|
||||
])
|
||||
)
|
||||
assert.equal(reusedWindow, window)
|
||||
|
||||
const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(editor => {
|
||||
send(editor.getPath())
|
||||
subscription.dispose()
|
||||
})
|
||||
})
|
||||
const activeEditorPath = await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
send => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(
|
||||
editor => {
|
||||
send(editor.getPath())
|
||||
subscription.dispose()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
assert([filePath1, filePath2].includes(activeEditorPath))
|
||||
assert.deepEqual(killedPids, [])
|
||||
@ -430,7 +593,9 @@ describe('AtomApplication', function () {
|
||||
await timeoutPromise(100)
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
let processKillPromise = new Promise(resolve => {
|
||||
onDidKillProcess = resolve
|
||||
})
|
||||
await evalInWebContents(window.browserWindow.webContents, send => {
|
||||
atom.workspace.getActivePaneItem().destroy()
|
||||
send()
|
||||
@ -438,7 +603,9 @@ describe('AtomApplication', function () {
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [102])
|
||||
|
||||
processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
processKillPromise = new Promise(resolve => {
|
||||
onDidKillProcess = resolve
|
||||
})
|
||||
window.close()
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [102, 101])
|
||||
@ -449,25 +616,40 @@ describe('AtomApplication', function () {
|
||||
await focusWindow(window)
|
||||
|
||||
const dirPath1 = makeTempDir()
|
||||
const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--add', '--wait', '--pid', '101', dirPath1]))
|
||||
const [reusedWindow] = await atomApplication.launch(
|
||||
parseCommandLine(['--add', '--wait', '--pid', '101', dirPath1])
|
||||
)
|
||||
assert.equal(reusedWindow, window)
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(window)).length === 1)
|
||||
await conditionPromise(
|
||||
async () => (await getTreeViewRootDirectories(window)).length === 1
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1])
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
const dirPath2 = makeTempDir()
|
||||
await evalInWebContents(window.browserWindow.webContents, (send, dirPath1, dirPath2) => {
|
||||
atom.project.setPaths([dirPath1, dirPath2])
|
||||
send()
|
||||
}, dirPath1, dirPath2)
|
||||
await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
(send, dirPath1, dirPath2) => {
|
||||
atom.project.setPaths([dirPath1, dirPath2])
|
||||
send()
|
||||
},
|
||||
dirPath1,
|
||||
dirPath2
|
||||
)
|
||||
await timeoutPromise(100)
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
await evalInWebContents(window.browserWindow.webContents, (send, dirPath2) => {
|
||||
atom.project.setPaths([dirPath2])
|
||||
send()
|
||||
}, dirPath2)
|
||||
let processKillPromise = new Promise(resolve => {
|
||||
onDidKillProcess = resolve
|
||||
})
|
||||
await evalInWebContents(
|
||||
window.browserWindow.webContents,
|
||||
(send, dirPath2) => {
|
||||
atom.project.setPaths([dirPath2])
|
||||
send()
|
||||
},
|
||||
dirPath2
|
||||
)
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [101])
|
||||
})
|
||||
@ -477,7 +659,9 @@ describe('AtomApplication', function () {
|
||||
if (process.platform === 'linux' || process.platform === 'win32') {
|
||||
it('quits the application', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir('a'), 'file-a')]))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine([path.join(makeTempDir('a'), 'file-a')])
|
||||
)
|
||||
await focusWindow(window)
|
||||
window.close()
|
||||
await window.closedPromise
|
||||
@ -487,7 +671,9 @@ describe('AtomApplication', function () {
|
||||
} else if (process.platform === 'darwin') {
|
||||
it('leaves the application open', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir('a'), 'file-a')]))
|
||||
const [window] = await atomApplication.launch(
|
||||
parseCommandLine([path.join(makeTempDir('a'), 'file-a')])
|
||||
)
|
||||
await focusWindow(window)
|
||||
window.close()
|
||||
await window.closedPromise
|
||||
@ -503,16 +689,29 @@ describe('AtomApplication', function () {
|
||||
const dirB = makeTempDir()
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window0] = await atomApplication.launch(parseCommandLine([dirA, dirB]))
|
||||
const [window0] = await atomApplication.launch(
|
||||
parseCommandLine([dirA, dirB])
|
||||
)
|
||||
await focusWindow(window0)
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(window0)).length === 2)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window0), [dirA, dirB])
|
||||
await conditionPromise(
|
||||
async () => (await getTreeViewRootDirectories(window0)).length === 2
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window0), [
|
||||
dirA,
|
||||
dirB
|
||||
])
|
||||
|
||||
const saveStatePromise = emitterEventPromise(atomApplication, 'application:did-save-state')
|
||||
await evalInWebContents(window0.browserWindow.webContents, (sendBackToMainProcess) => {
|
||||
atom.project.removePath(atom.project.getPaths()[0])
|
||||
sendBackToMainProcess(null)
|
||||
})
|
||||
const saveStatePromise = emitterEventPromise(
|
||||
atomApplication,
|
||||
'application:did-save-state'
|
||||
)
|
||||
await evalInWebContents(
|
||||
window0.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.project.removePath(atom.project.getPaths()[0])
|
||||
sendBackToMainProcess(null)
|
||||
}
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window0), [dirB])
|
||||
await saveStatePromise
|
||||
|
||||
@ -520,17 +719,29 @@ describe('AtomApplication', function () {
|
||||
const atomApplication2 = buildAtomApplication()
|
||||
const [window2] = await atomApplication2.launch(parseCommandLine([]))
|
||||
await focusWindow(window2)
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(window2)).length === 1)
|
||||
await conditionPromise(
|
||||
async () => (await getTreeViewRootDirectories(window2)).length === 1
|
||||
)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when opening atom:// URLs', () => {
|
||||
it('loads the urlMain file in a new window', async () => {
|
||||
const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-url-main')
|
||||
const packagePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'fixtures',
|
||||
'packages',
|
||||
'package-with-url-main'
|
||||
)
|
||||
const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages')
|
||||
fs.mkdirSync(packagesDirPath)
|
||||
fs.symlinkSync(packagePath, path.join(packagesDirPath, 'package-with-url-main'), 'junction')
|
||||
fs.symlinkSync(
|
||||
packagePath,
|
||||
path.join(packagesDirPath, 'package-with-url-main'),
|
||||
'junction'
|
||||
)
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
const launchOptions = parseCommandLine([])
|
||||
@ -538,9 +749,12 @@ describe('AtomApplication', function () {
|
||||
let [windows] = await atomApplication.launch(launchOptions)
|
||||
await windows[0].loadedPromise
|
||||
|
||||
let reached = await evalInWebContents(windows[0].browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(global.reachedUrlMain)
|
||||
})
|
||||
let reached = await evalInWebContents(
|
||||
windows[0].browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
sendBackToMainProcess(global.reachedUrlMain)
|
||||
}
|
||||
)
|
||||
assert.isTrue(reached)
|
||||
windows[0].close()
|
||||
})
|
||||
@ -550,9 +764,13 @@ describe('AtomApplication', function () {
|
||||
const dirBPath = makeTempDir('b')
|
||||
|
||||
const atomApplication = buildAtomApplication()
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath)]))
|
||||
const [window1] = await atomApplication.launch(
|
||||
parseCommandLine([path.join(dirAPath)])
|
||||
)
|
||||
await focusWindow(window1)
|
||||
const [window2] = await atomApplication.launch(parseCommandLine([path.join(dirBPath)]))
|
||||
const [window2] = await atomApplication.launch(
|
||||
parseCommandLine([path.join(dirBPath)])
|
||||
)
|
||||
await focusWindow(window2)
|
||||
|
||||
const fileA = path.join(dirAPath, 'file-a')
|
||||
@ -564,10 +782,16 @@ describe('AtomApplication', function () {
|
||||
sinon.spy(window2, 'sendURIMessage')
|
||||
|
||||
atomApplication.launch(parseCommandLine(['--uri-handler', uriA]))
|
||||
await conditionPromise(() => window1.sendURIMessage.calledWith(uriA), `window1 to be focused from ${fileA}`)
|
||||
await conditionPromise(
|
||||
() => window1.sendURIMessage.calledWith(uriA),
|
||||
`window1 to be focused from ${fileA}`
|
||||
)
|
||||
|
||||
atomApplication.launch(parseCommandLine(['--uri-handler', uriB]))
|
||||
await conditionPromise(() => window2.sendURIMessage.calledWith(uriB), `window2 to be focused from ${fileB}`)
|
||||
await conditionPromise(
|
||||
() => window2.sendURIMessage.calledWith(uriB),
|
||||
`window2 to be focused from ${fileB}`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -584,7 +808,10 @@ describe('AtomApplication', function () {
|
||||
await new Promise(process.nextTick)
|
||||
assert(!electron.app.didQuit())
|
||||
|
||||
await Promise.all([window1.lastPrepareToUnloadPromise, window2.lastPrepareToUnloadPromise])
|
||||
await Promise.all([
|
||||
window1.lastPrepareToUnloadPromise,
|
||||
window2.lastPrepareToUnloadPromise
|
||||
])
|
||||
assert(!electron.app.didQuit())
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
await new Promise(process.nextTick)
|
||||
@ -596,20 +823,23 @@ describe('AtomApplication', function () {
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([]))
|
||||
const [window2] = await atomApplication.launch(parseCommandLine([]))
|
||||
await Promise.all([window1.loadedPromise, window2.loadedPromise])
|
||||
await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.getActiveTextEditor().insertText('unsaved text')
|
||||
sendBackToMainProcess()
|
||||
})
|
||||
await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.getActiveTextEditor().insertText('unsaved text')
|
||||
sendBackToMainProcess()
|
||||
}
|
||||
)
|
||||
|
||||
// Choosing "Cancel"
|
||||
mockElectronShowMessageBox({response: 1})
|
||||
mockElectronShowMessageBox({ response: 1 })
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(!electron.app.didQuit())
|
||||
assert.equal(electron.app.quit.callCount, 1) // Ensure choosing "Cancel" doesn't try to quit the electron app more than once (regression)
|
||||
|
||||
// Choosing "Don't save"
|
||||
mockElectronShowMessageBox({response: 2})
|
||||
mockElectronShowMessageBox({ response: 2 })
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(electron.app.didQuit())
|
||||
@ -620,19 +850,22 @@ describe('AtomApplication', function () {
|
||||
const [window1] = await atomApplication.launch(parseCommandLine([]))
|
||||
const [window2] = await atomApplication.launch(parseCommandLine([]))
|
||||
await Promise.all([window1.loadedPromise, window2.loadedPromise])
|
||||
await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.getActiveTextEditor().insertText('unsaved text')
|
||||
sendBackToMainProcess()
|
||||
})
|
||||
await evalInWebContents(
|
||||
window1.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.getActiveTextEditor().insertText('unsaved text')
|
||||
sendBackToMainProcess()
|
||||
}
|
||||
)
|
||||
|
||||
// Choosing "Cancel"
|
||||
mockElectronShowMessageBox({response: 1})
|
||||
mockElectronShowMessageBox({ response: 1 })
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(atomApplication.getAllWindows().length === 1)
|
||||
|
||||
// Choosing "Don't save"
|
||||
mockElectronShowMessageBox({response: 2})
|
||||
mockElectronShowMessageBox({ response: 2 })
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(atomApplication.getAllWindows().length === 0)
|
||||
@ -650,24 +883,35 @@ describe('AtomApplication', function () {
|
||||
await window.closedPromise
|
||||
|
||||
atomApplication.emit('application:open')
|
||||
await conditionPromise(() => atomApplication.promptForPathToOpen.calledWith('all'))
|
||||
await conditionPromise(() =>
|
||||
atomApplication.promptForPathToOpen.calledWith('all')
|
||||
)
|
||||
atomApplication.promptForPathToOpen.reset()
|
||||
|
||||
atomApplication.emit('application:open-file')
|
||||
await conditionPromise(() => atomApplication.promptForPathToOpen.calledWith('file'))
|
||||
await conditionPromise(() =>
|
||||
atomApplication.promptForPathToOpen.calledWith('file')
|
||||
)
|
||||
atomApplication.promptForPathToOpen.reset()
|
||||
|
||||
atomApplication.emit('application:open-folder')
|
||||
await conditionPromise(() => atomApplication.promptForPathToOpen.calledWith('folder'))
|
||||
await conditionPromise(() =>
|
||||
atomApplication.promptForPathToOpen.calledWith('folder')
|
||||
)
|
||||
atomApplication.promptForPathToOpen.reset()
|
||||
})
|
||||
}
|
||||
|
||||
function buildAtomApplication (params = {}) {
|
||||
const atomApplication = new AtomApplication(Object.assign({
|
||||
resourcePath: ATOM_RESOURCE_PATH,
|
||||
atomHomeDirPath: process.env.ATOM_HOME
|
||||
}, params))
|
||||
const atomApplication = new AtomApplication(
|
||||
Object.assign(
|
||||
{
|
||||
resourcePath: ATOM_RESOURCE_PATH,
|
||||
atomHomeDirPath: process.env.ATOM_HOME
|
||||
},
|
||||
params
|
||||
)
|
||||
)
|
||||
atomApplicationsToDestroy.push(atomApplication)
|
||||
return atomApplication
|
||||
}
|
||||
@ -675,7 +919,9 @@ describe('AtomApplication', function () {
|
||||
async function focusWindow (window) {
|
||||
window.focus()
|
||||
await window.loadedPromise
|
||||
await conditionPromise(() => window.atomApplication.getLastFocusedWindow() === window)
|
||||
await conditionPromise(
|
||||
() => window.atomApplication.getLastFocusedWindow() === window
|
||||
)
|
||||
}
|
||||
|
||||
function mockElectronAppQuit () {
|
||||
@ -684,7 +930,11 @@ describe('AtomApplication', function () {
|
||||
electron.app.quit = function () {
|
||||
this.quit.callCount++
|
||||
let defaultPrevented = false
|
||||
this.emit('before-quit', {preventDefault () { defaultPrevented = true }})
|
||||
this.emit('before-quit', {
|
||||
preventDefault () {
|
||||
defaultPrevented = true
|
||||
}
|
||||
})
|
||||
if (!defaultPrevented) didQuit = true
|
||||
}
|
||||
|
||||
@ -693,7 +943,7 @@ describe('AtomApplication', function () {
|
||||
electron.app.didQuit = () => didQuit
|
||||
}
|
||||
|
||||
function mockElectronShowMessageBox ({response}) {
|
||||
function mockElectronShowMessageBox ({ response }) {
|
||||
electron.dialog.showMessageBox = (window, options, callback) => {
|
||||
callback(response)
|
||||
}
|
||||
@ -718,7 +968,9 @@ describe('AtomApplication', function () {
|
||||
function sendBackToMainProcess (result) {
|
||||
require('electron').ipcRenderer.send('${channelId}', result)
|
||||
}
|
||||
(${source})(sendBackToMainProcess, ${args.map(JSON.stringify).join(', ')})
|
||||
(${source})(sendBackToMainProcess, ${args
|
||||
.map(JSON.stringify)
|
||||
.join(', ')})
|
||||
`
|
||||
// console.log(`about to execute:\n${js}`)
|
||||
|
||||
@ -727,19 +979,24 @@ describe('AtomApplication', function () {
|
||||
}
|
||||
|
||||
function getTreeViewRootDirectories (atomWindow) {
|
||||
return evalInWebContents(atomWindow.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.getLeftDock().observeActivePaneItem((treeView) => {
|
||||
if (treeView) {
|
||||
sendBackToMainProcess(
|
||||
Array
|
||||
.from(treeView.element.querySelectorAll('.project-root > .header .name'))
|
||||
.map(element => element.dataset.path)
|
||||
)
|
||||
} else {
|
||||
sendBackToMainProcess([])
|
||||
}
|
||||
})
|
||||
})
|
||||
return evalInWebContents(
|
||||
atomWindow.browserWindow.webContents,
|
||||
sendBackToMainProcess => {
|
||||
atom.workspace.getLeftDock().observeActivePaneItem(treeView => {
|
||||
if (treeView) {
|
||||
sendBackToMainProcess(
|
||||
Array.from(
|
||||
treeView.element.querySelectorAll(
|
||||
'.project-root > .header .name'
|
||||
)
|
||||
).map(element => element.dataset.path)
|
||||
)
|
||||
} else {
|
||||
sendBackToMainProcess([])
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function clearElectronSession () {
|
||||
|
@ -1,13 +1,14 @@
|
||||
const {dialog} = require('electron')
|
||||
const { dialog } = require('electron')
|
||||
const FileRecoveryService = require('../../src/main-process/file-recovery-service')
|
||||
const fs = require('fs-plus')
|
||||
const fsreal = require('fs')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const { assert } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const {escapeRegExp} = require('underscore-plus')
|
||||
const { escapeRegExp } = require('underscore-plus')
|
||||
const temp = require('temp').track()
|
||||
|
||||
describe("FileRecoveryService", () => {
|
||||
describe('FileRecoveryService', () => {
|
||||
let recoveryService, recoveryDirectory, spies
|
||||
|
||||
beforeEach(() => {
|
||||
@ -25,90 +26,90 @@ describe("FileRecoveryService", () => {
|
||||
}
|
||||
})
|
||||
|
||||
describe("when no crash happens during a save", () => {
|
||||
it("creates a recovery file and deletes it after saving", async () => {
|
||||
describe('when no crash happens during a save', () => {
|
||||
it('creates a recovery file and deletes it after saving', async () => {
|
||||
const mockWindow = {}
|
||||
const filePath = temp.path()
|
||||
|
||||
fs.writeFileSync(filePath, "some content")
|
||||
fs.writeFileSync(filePath, 'some content')
|
||||
await recoveryService.willSavePath(mockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 1)
|
||||
|
||||
fs.writeFileSync(filePath, "changed")
|
||||
fs.writeFileSync(filePath, 'changed')
|
||||
await recoveryService.didSavePath(mockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), "changed")
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed')
|
||||
|
||||
fs.removeSync(filePath)
|
||||
})
|
||||
|
||||
it("creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it", async () => {
|
||||
it('creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it', async () => {
|
||||
const mockWindow = {}
|
||||
const anotherMockWindow = {}
|
||||
const filePath = temp.path()
|
||||
|
||||
fs.writeFileSync(filePath, "some content")
|
||||
fs.writeFileSync(filePath, 'some content')
|
||||
await recoveryService.willSavePath(mockWindow, filePath)
|
||||
await recoveryService.willSavePath(anotherMockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 1)
|
||||
|
||||
fs.writeFileSync(filePath, "changed")
|
||||
fs.writeFileSync(filePath, 'changed')
|
||||
await recoveryService.didSavePath(mockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 1)
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), "changed")
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed')
|
||||
|
||||
await recoveryService.didSavePath(anotherMockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), "changed")
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), 'changed')
|
||||
|
||||
fs.removeSync(filePath)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when a crash happens during a save", () => {
|
||||
it("restores the created recovery file and deletes it", async () => {
|
||||
describe('when a crash happens during a save', () => {
|
||||
it('restores the created recovery file and deletes it', async () => {
|
||||
const mockWindow = {}
|
||||
const filePath = temp.path()
|
||||
|
||||
fs.writeFileSync(filePath, "some content")
|
||||
fs.writeFileSync(filePath, 'some content')
|
||||
await recoveryService.willSavePath(mockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 1)
|
||||
|
||||
fs.writeFileSync(filePath, "changed")
|
||||
fs.writeFileSync(filePath, 'changed')
|
||||
await recoveryService.didCrashWindow(mockWindow)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), "some content")
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), 'some content')
|
||||
|
||||
fs.removeSync(filePath)
|
||||
})
|
||||
|
||||
it("restores the created recovery file when many windows attempt to save the same file and one of them crashes", async () => {
|
||||
it('restores the created recovery file when many windows attempt to save the same file and one of them crashes', async () => {
|
||||
const mockWindow = {}
|
||||
const anotherMockWindow = {}
|
||||
const filePath = temp.path()
|
||||
|
||||
fs.writeFileSync(filePath, "A")
|
||||
fs.writeFileSync(filePath, 'A')
|
||||
await recoveryService.willSavePath(mockWindow, filePath)
|
||||
fs.writeFileSync(filePath, "B")
|
||||
fs.writeFileSync(filePath, 'B')
|
||||
await recoveryService.willSavePath(anotherMockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 1)
|
||||
|
||||
fs.writeFileSync(filePath, "C")
|
||||
fs.writeFileSync(filePath, 'C')
|
||||
|
||||
await recoveryService.didCrashWindow(mockWindow)
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), "A")
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), 'A')
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
|
||||
fs.writeFileSync(filePath, "D")
|
||||
fs.writeFileSync(filePath, 'D')
|
||||
await recoveryService.willSavePath(mockWindow, filePath)
|
||||
fs.writeFileSync(filePath, "E")
|
||||
fs.writeFileSync(filePath, 'E')
|
||||
await recoveryService.willSavePath(anotherMockWindow, filePath)
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 1)
|
||||
|
||||
fs.writeFileSync(filePath, "F")
|
||||
fs.writeFileSync(filePath, 'F')
|
||||
|
||||
await recoveryService.didCrashWindow(anotherMockWindow)
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), "D")
|
||||
assert.equal(fs.readFileSync(filePath, 'utf8'), 'D')
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
|
||||
fs.removeSync(filePath)
|
||||
@ -117,10 +118,10 @@ describe("FileRecoveryService", () => {
|
||||
it("emits a warning when a file can't be recovered", async () => {
|
||||
const mockWindow = {}
|
||||
const filePath = temp.path()
|
||||
fs.writeFileSync(filePath, "content")
|
||||
fs.writeFileSync(filePath, 'content')
|
||||
|
||||
let logs = []
|
||||
spies.stub(console, 'log', (message) => logs.push(message))
|
||||
spies.stub(console, 'log', message => logs.push(message))
|
||||
spies.stub(dialog, 'showMessageBox')
|
||||
|
||||
// Copy files to be recovered before mocking fs.createWriteStream
|
||||
@ -130,9 +131,15 @@ describe("FileRecoveryService", () => {
|
||||
// attempting to copy the recovered file to its original location
|
||||
var fakeEmitter = new EventEmitter()
|
||||
var onStub = spies.stub(fakeEmitter, 'on')
|
||||
onStub.withArgs('error').yields(new Error('Nope')).returns(fakeEmitter)
|
||||
onStub
|
||||
.withArgs('error')
|
||||
.yields(new Error('Nope'))
|
||||
.returns(fakeEmitter)
|
||||
onStub.withArgs('open').returns(fakeEmitter)
|
||||
spies.stub(fsreal, 'createWriteStream').withArgs(filePath).returns(fakeEmitter)
|
||||
spies
|
||||
.stub(fsreal, 'createWriteStream')
|
||||
.withArgs(filePath)
|
||||
.returns(fakeEmitter)
|
||||
|
||||
await recoveryService.didCrashWindow(mockWindow)
|
||||
let recoveryFiles = fs.listTreeSync(recoveryDirectory)
|
||||
@ -148,10 +155,10 @@ describe("FileRecoveryService", () => {
|
||||
it("doesn't create a recovery file when the file that's being saved doesn't exist yet", async () => {
|
||||
const mockWindow = {}
|
||||
|
||||
await recoveryService.willSavePath(mockWindow, "a-file-that-doesnt-exist")
|
||||
await recoveryService.willSavePath(mockWindow, 'a-file-that-doesnt-exist')
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
|
||||
await recoveryService.didSavePath(mockWindow, "a-file-that-doesnt-exist")
|
||||
await recoveryService.didSavePath(mockWindow, 'a-file-that-doesnt-exist')
|
||||
assert.equal(fs.listTreeSync(recoveryDirectory).length, 0)
|
||||
})
|
||||
})
|
||||
|
@ -1,9 +1,8 @@
|
||||
const Mocha = require('mocha')
|
||||
const fs = require('fs-plus')
|
||||
const {assert} = require('chai')
|
||||
const { assert } = require('chai')
|
||||
|
||||
module.exports =
|
||||
function (testPaths) {
|
||||
module.exports = function (testPaths) {
|
||||
global.assert = assert
|
||||
|
||||
let reporterOptions = {
|
||||
|
@ -1,9 +1,17 @@
|
||||
const { assert } = require('chai')
|
||||
const parseCommandLine = require('../../src/main-process/parse-command-line')
|
||||
|
||||
describe('parseCommandLine', () => {
|
||||
describe('when --uri-handler is not passed', () => {
|
||||
it('parses arguments as normal', () => {
|
||||
const args = parseCommandLine(['-d', '--safe', '--test', '/some/path', 'atom://test/url', 'atom://other/url'])
|
||||
const args = parseCommandLine([
|
||||
'-d',
|
||||
'--safe',
|
||||
'--test',
|
||||
'/some/path',
|
||||
'atom://test/url',
|
||||
'atom://other/url'
|
||||
])
|
||||
assert.isTrue(args.devMode)
|
||||
assert.isTrue(args.safeMode)
|
||||
assert.isTrue(args.test)
|
||||
@ -14,7 +22,15 @@ describe('parseCommandLine', () => {
|
||||
|
||||
describe('when --uri-handler is passed', () => {
|
||||
it('ignores other arguments and limits to one URL', () => {
|
||||
const args = parseCommandLine(['-d', '--uri-handler', '--safe', '--test', '/some/path', 'atom://test/url', 'atom://other/url'])
|
||||
const args = parseCommandLine([
|
||||
'-d',
|
||||
'--uri-handler',
|
||||
'--safe',
|
||||
'--test',
|
||||
'/some/path',
|
||||
'atom://test/url',
|
||||
'atom://other/url'
|
||||
])
|
||||
assert.isUndefined(args.devMode)
|
||||
assert.isUndefined(args.safeMode)
|
||||
assert.isUndefined(args.test)
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {sortMenuItems} = require('../src/menu-sort-helpers')
|
||||
const { sortMenuItems } = require('../src/menu-sort-helpers')
|
||||
|
||||
describe('contextMenu', () => {
|
||||
describe('dedupes separators', () => {
|
||||
@ -18,7 +18,8 @@ describe('contextMenu', () => {
|
||||
it('preserves separators at the begining of set two', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' }, { command: 'core:two' }
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
@ -36,8 +37,10 @@ describe('contextMenu', () => {
|
||||
|
||||
it('removes duplicate separators across sets', () => {
|
||||
const items = [
|
||||
{ command: 'core:one' }, { type: 'separator' },
|
||||
{ type: 'separator' }, { command: 'core:two' }
|
||||
{ command: 'core:one' },
|
||||
{ type: 'separator' },
|
||||
{ type: 'separator' },
|
||||
{ command: 'core:two' }
|
||||
]
|
||||
const expected = [
|
||||
{ command: 'core:one' },
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user