diff --git a/spec/body-parser-spec.coffee b/spec/body-parser-spec.js
similarity index 52%
rename from spec/body-parser-spec.coffee
rename to spec/body-parser-spec.js
index 2f8a28e7..75623f2c 100644
--- a/spec/body-parser-spec.coffee
+++ b/spec/body-parser-spec.js
@@ -1,13 +1,12 @@
-BodyParser = require '../lib/snippet-body-parser'
-
-describe "Snippet Body Parser", ->
- it "breaks a snippet body into lines, with each line containing tab stops at the appropriate position", ->
- bodyTree = BodyParser.parse """
- the quick brown $1fox ${2:jumped ${3:over}
- }the ${4:lazy} dog
- """
-
- expect(bodyTree).toEqual [
+const BodyParser = require('../lib/snippet-body-parser');
+
+describe("Snippet Body Parser", () => {
+ it("breaks a snippet body into lines, with each line containing tab stops at the appropriate position", () => {
+ const bodyTree = BodyParser.parse(
+ "the quick brown $1fox ${2:jumped ${3:over}\n" +
+ "}the ${4:lazy} dog"
+ );
+ expect(bodyTree).toEqual([
"the quick brown ",
{index: 1, content: []},
"fox ",
@@ -18,31 +17,31 @@ describe "Snippet Body Parser", ->
{index: 3, content: ["over"]},
"\n"
],
- }
- "the "
+ },
+ "the ",
{index: 4, content: ["lazy"]},
" dog"
- ]
-
- it "removes interpolated variables in placeholder text (we don't currently support it)", ->
- bodyTree = BodyParser.parse """
- module ${1:ActiveRecord::${TM_FILENAME/(?:\\A|_)([A-Za-z0-9]+)(?:\\.rb)?/(?2::\\u$1)/g}}
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("removes interpolated variables in placeholder text (we don't currently support it)", () => {
+ const bodyTree = BodyParser.parse(
+ "module ${1:ActiveRecord::${TM_FILENAME/(?:\\A|_)([A-Za-z0-9]+)(?:\\.rb)?/(?2::\\u$1)/g}}"
+ );
+ expect(bodyTree).toEqual([
"module ",
{
"index": 1,
"content": ["ActiveRecord::", ""]
}
- ]
-
- it "skips escaped tabstops", ->
- bodyTree = BodyParser.parse """
- snippet $1 escaped \\$2 \\\\$3
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("skips escaped tabstops", () => {
+ const bodyTree = BodyParser.parse(
+ "snippet $1 escaped \\$2 \\\\$3"
+ );
+ expect(bodyTree).toEqual([
"snippet ",
{
index: 1,
@@ -53,26 +52,27 @@ describe "Snippet Body Parser", ->
index: 3,
content: []
}
- ]
-
- it "includes escaped right-braces", ->
- bodyTree = BodyParser.parse """
- snippet ${1:{\\}}
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("includes escaped right-braces", () => {
+ const bodyTree = BodyParser.parse(
+ "snippet ${1:{\\}}"
+ );
+ expect(bodyTree).toEqual([
"snippet ",
{
index: 1,
content: ["{}"]
}
- ]
-
- it "parses a snippet with transformations", ->
- bodyTree = BodyParser.parse """
- <${1:p}>$0${1/f/F/}>
- """
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("parses a snippet with transformations", () => {
+ const bodyTree = BodyParser.parse(
+ "<${1:p}>$0${1/f/F/}>"
+ );
+ expect(bodyTree).toEqual([
'<',
{index: 1, content: ['p']},
'>',
@@ -80,14 +80,14 @@ describe "Snippet Body Parser", ->
'',
{index: 1, content: [], substitution: {find: /f/g, replace: ['F']}},
'>'
- ]
-
- it "parses a snippet with multiple tab stops with transformations", ->
- bodyTree = BodyParser.parse """
- ${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("parses a snippet with multiple tab stops with transformations", () => {
+ const bodyTree = BodyParser.parse(
+ "${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2"
+ );
+ expect(bodyTree).toEqual([
{index: 1, content: ['placeholder']},
' ',
{
@@ -119,15 +119,15 @@ describe "Snippet Body Parser", ->
},
' ',
{index: 2, content: []},
- ]
-
+ ]);
+});
- it "parses a snippet with transformations and mirrors", ->
- bodyTree = BodyParser.parse """
- ${1:placeholder}\n${1/(.)/\\u$1/}\n$1
- """
- expect(bodyTree).toEqual [
+ it("parses a snippet with transformations and mirrors", () => {
+ const bodyTree = BodyParser.parse(
+ "${1:placeholder}\n\${1/(.)/\\u$1/}\n$1"
+ );
+ expect(bodyTree).toEqual([
{index: 1, content: ['placeholder']},
'\n',
{
@@ -143,14 +143,14 @@ describe "Snippet Body Parser", ->
},
'\n',
{index: 1, content: []}
- ]
-
- it "parses a snippet with a format string and case-control flags", ->
- bodyTree = BodyParser.parse """
- <${1:p}>$0${1/(.)(.*)/\\u$1$2/}>
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("parses a snippet with a format string and case-control flags", () => {
+ const bodyTree = BodyParser.parse(
+ "<${1:p}>$0${1/(.)(.*)/\\u$1$2/}>"
+ );
+ expect(bodyTree).toEqual([
'<',
{index: 1, content: ['p']},
'>',
@@ -169,16 +169,16 @@ describe "Snippet Body Parser", ->
}
},
'>'
- ]
-
- it "parses a snippet with an escaped forward slash in a transform", ->
- # Annoyingly, a forward slash needs to be double-backslashed just like the
- # other escapes.
- bodyTree = BodyParser.parse """
- <${1:p}>$0${1/(.)\\/(.*)/\\u$1$2/}>
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("parses a snippet with an escaped forward slash in a transform", () => {
+ // Annoyingly, a forward slash needs to be double-backslashed just like the
+ // other escapes.
+ const bodyTree = BodyParser.parse(
+ "<${1:p}>$0${1/(.)\\/(.*)/\\u$1$2/}>"
+ );
+ expect(bodyTree).toEqual([
'<',
{index: 1, content: ['p']},
'>',
@@ -197,14 +197,14 @@ describe "Snippet Body Parser", ->
}
},
'>'
- ]
-
- it "parses a snippet with a placeholder that mirrors another tab stop's content", ->
- bodyTree = BodyParser.parse """
- $4console.${3:log}('${2:$1}', $1);$0
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("parses a snippet with a placeholder that mirrors another tab stop's content", () => {
+ const bodyTree = BodyParser.parse(
+ "$4console.${3:log}('${2:$1}', $1);$0"
+ );
+ expect(bodyTree).toEqual([
{index: 4, content: []},
'console.',
{index: 3, content: ['log']},
@@ -218,14 +218,14 @@ describe "Snippet Body Parser", ->
{index: 1, content: []},
');',
{index: 0, content: []}
- ]
-
- it "parses a snippet with a placeholder that mixes text and tab stop references", ->
- bodyTree = BodyParser.parse """
- $4console.${3:log}('${2:uh $1}', $1);$0
- """
-
- expect(bodyTree).toEqual [
+ ]);
+ });
+
+ it("parses a snippet with a placeholder that mixes text and tab stop references", () => {
+ const bodyTree = BodyParser.parse(
+ "$4console.${3:log}('${2:uh $1}', $1);$0"
+ );
+ expect(bodyTree).toEqual([
{index: 4, content: []},
'console.',
{index: 3, content: ['log']},
@@ -240,4 +240,6 @@ describe "Snippet Body Parser", ->
{index: 1, content: []},
');',
{index: 0, content: []}
- ]
+ ]);
+ });
+});
diff --git a/spec/snippet-loading-spec.coffee b/spec/snippet-loading-spec.coffee
deleted file mode 100644
index 12a6c2aa..00000000
--- a/spec/snippet-loading-spec.coffee
+++ /dev/null
@@ -1,268 +0,0 @@
-path = require 'path'
-fs = require 'fs-plus'
-temp = require('temp').track()
-
-describe "Snippet Loading", ->
- [configDirPath, snippetsService] = []
-
- beforeEach ->
- configDirPath = temp.mkdirSync('atom-config-dir-')
- spyOn(atom, 'getConfigDirPath').andReturn configDirPath
-
- spyOn(console, 'warn')
- spyOn(atom.notifications, 'addError') if atom.notifications?
-
- spyOn(atom.packages, 'getLoadedPackages').andReturn [
- atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-snippets'))
- atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-broken-snippets')),
- ]
-
- afterEach ->
- waitsForPromise ->
- Promise.resolve(atom.packages.deactivatePackages('snippets'))
- runs ->
- jasmine.unspy(atom.packages, 'getLoadedPackages')
-
- activateSnippetsPackage = ->
- waitsForPromise ->
- atom.packages.activatePackage("snippets").then ({mainModule}) ->
- snippetsService = mainModule.provideSnippets()
- mainModule.loaded = false
-
- waitsFor "all snippets to load", 3000, ->
- snippetsService.bundledSnippetsLoaded()
-
- it "loads the bundled snippet template snippets", ->
- activateSnippetsPackage()
-
- runs ->
- jsonSnippet = snippetsService.snippetsForScopes(['.source.json'])['snip']
- expect(jsonSnippet.name).toBe 'Atom Snippet'
- expect(jsonSnippet.prefix).toBe 'snip'
- expect(jsonSnippet.body).toContain '"prefix":'
- expect(jsonSnippet.body).toContain '"body":'
- expect(jsonSnippet.tabStopList.length).toBeGreaterThan(0)
-
- csonSnippet = snippetsService.snippetsForScopes(['.source.coffee'])['snip']
- expect(csonSnippet.name).toBe 'Atom Snippet'
- expect(csonSnippet.prefix).toBe 'snip'
- expect(csonSnippet.body).toContain "'prefix':"
- expect(csonSnippet.body).toContain "'body':"
- expect(csonSnippet.tabStopList.length).toBeGreaterThan(0)
-
- it "loads non-hidden snippet files from atom packages with snippets directories", ->
- activateSnippetsPackage()
-
- runs ->
- snippet = snippetsService.snippetsForScopes(['.test'])['test']
- expect(snippet.prefix).toBe 'test'
- expect(snippet.body).toBe 'testing 123'
-
- snippet = snippetsService.snippetsForScopes(['.test'])['testd']
- expect(snippet.prefix).toBe 'testd'
- expect(snippet.body).toBe 'testing 456'
- expect(snippet.description).toBe 'a description'
- expect(snippet.descriptionMoreURL).toBe 'http://google.com'
-
- snippet = snippetsService.snippetsForScopes(['.test'])['testlabelleft']
- expect(snippet.prefix).toBe 'testlabelleft'
- expect(snippet.body).toBe 'testing 456'
- expect(snippet.leftLabel).toBe 'a label'
-
- snippet = snippetsService.snippetsForScopes(['.test'])['testhtmllabels']
- expect(snippet.prefix).toBe 'testhtmllabels'
- expect(snippet.body).toBe 'testing 456'
- expect(snippet.leftLabelHTML).toBe 'Label'
- expect(snippet.rightLabelHTML).toBe 'Label'
-
- it "logs a warning if package snippets files cannot be parsed", ->
- activateSnippetsPackage()
-
- runs ->
- # Warn about invalid-file, but don't even try to parse a hidden file
- expect(console.warn.calls.length).toBeGreaterThan 0
- expect(console.warn.mostRecentCall.args[0]).toMatch(/Error reading.*package-with-broken-snippets/)
-
- describe "::loadPackageSnippets(callback)", ->
- beforeEach ->
- # simulate a list of packages where the javascript core package is returned at the end
- atom.packages.getLoadedPackages.andReturn [
- atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-snippets'))
- atom.packages.loadPackage('language-javascript')
- ]
-
- it "allows other packages to override core packages' snippets", ->
- waitsForPromise ->
- atom.packages.activatePackage("language-javascript")
-
- activateSnippetsPackage()
-
- runs ->
- snippet = snippetsService.snippetsForScopes(['.source.js'])['log']
- expect(snippet.body).toBe "from-a-community-package"
-
- describe "::onDidLoadSnippets(callback)", ->
- it "invokes listeners when all snippets are loaded", ->
- loadedCallback = null
-
- waitsFor "package to activate", (done) ->
- atom.packages.activatePackage("snippets").then ({mainModule}) ->
- mainModule.onDidLoadSnippets(loadedCallback = jasmine.createSpy('onDidLoadSnippets callback'))
- done()
-
- waitsFor "onDidLoad callback to be called", -> loadedCallback.callCount > 0
-
- describe "when ~/.atom/snippets.json exists", ->
- beforeEach ->
- fs.writeFileSync path.join(configDirPath, 'snippets.json'), """
- {
- ".foo": {
- "foo snippet": {
- "prefix": "foo",
- "body": "bar1"
- }
- }
- }
- """
- activateSnippetsPackage()
-
- it "loads the snippets from that file", ->
- snippet = null
-
- waitsFor ->
- snippet = snippetsService.snippetsForScopes(['.foo'])['foo']
-
- runs ->
- expect(snippet.name).toBe 'foo snippet'
- expect(snippet.prefix).toBe "foo"
- expect(snippet.body).toBe "bar1"
-
- describe "when that file changes", ->
- it "reloads the snippets", ->
- fs.writeFileSync path.join(configDirPath, 'snippets.json'), """
- {
- ".foo": {
- "foo snippet": {
- "prefix": "foo",
- "body": "bar2"
- }
- }
- }
- """
-
- waitsFor "snippets to be changed", ->
- snippet = snippetsService.snippetsForScopes(['.foo'])['foo']
- snippet?.body is 'bar2'
-
- runs ->
- fs.writeFileSync path.join(configDirPath, 'snippets.json'), ""
-
- waitsFor "snippets to be removed", ->
- not snippetsService.snippetsForScopes(['.foo'])['foo']
-
- describe "when ~/.atom/snippets.cson exists", ->
- beforeEach ->
- fs.writeFileSync path.join(configDirPath, 'snippets.cson'), """
- ".foo":
- "foo snippet":
- "prefix": "foo"
- "body": "bar1"
- """
- activateSnippetsPackage()
-
- it "loads the snippets from that file", ->
- snippet = null
-
- waitsFor ->
- snippet = snippetsService.snippetsForScopes(['.foo'])['foo']
-
- runs ->
- expect(snippet.name).toBe 'foo snippet'
- expect(snippet.prefix).toBe "foo"
- expect(snippet.body).toBe "bar1"
-
- describe "when that file changes", ->
- it "reloads the snippets", ->
- fs.writeFileSync path.join(configDirPath, 'snippets.cson'), """
- ".foo":
- "foo snippet":
- "prefix": "foo"
- "body": "bar2"
- """
-
- waitsFor "snippets to be changed", ->
- snippet = snippetsService.snippetsForScopes(['.foo'])['foo']
- snippet?.body is 'bar2'
-
- runs ->
- fs.writeFileSync path.join(configDirPath, 'snippets.cson'), ""
-
- waitsFor "snippets to be removed", ->
- snippet = snippetsService.snippetsForScopes(['.foo'])['foo']
- not snippet?
-
- it "notifies the user when the user snippets file cannot be loaded", ->
- fs.writeFileSync path.join(configDirPath, 'snippets.cson'), """
- ".junk":::
- """
-
- activateSnippetsPackage()
-
- runs ->
- expect(console.warn).toHaveBeenCalled()
- expect(atom.notifications.addError).toHaveBeenCalled() if atom.notifications?
-
- describe "packages-with-snippets-disabled feature", ->
- it "disables no snippets if the config option is empty", ->
- originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
- atom.config.set('core.packagesWithSnippetsDisabled', [])
-
- activateSnippetsPackage()
- runs ->
- snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
- expect(Object.keys(snippets).length).toBe 1
- atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)
-
- it "still includes a disabled package's snippets in the list of unparsed snippets", ->
- originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
- atom.config.set('core.packagesWithSnippetsDisabled', [])
-
- activateSnippetsPackage()
- runs ->
- atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])
- allSnippets = snippetsService.getUnparsedSnippets()
- scopedSnippet = allSnippets.find (s) ->
- s.selectorString is '.package-with-snippets-unique-scope'
- expect(scopedSnippet).not.toBe undefined
- originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
-
- it "never loads a package's snippets when that package is disabled in config", ->
- originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
- atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])
-
- activateSnippetsPackage()
- runs ->
- snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
- expect(Object.keys(snippets).length).toBe 0
- atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)
-
- it "unloads and/or reloads snippets from a package if the config option is changed after activation", ->
- originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
- atom.config.set('core.packagesWithSnippetsDisabled', [])
-
- activateSnippetsPackage()
- runs ->
- snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
- expect(Object.keys(snippets).length).toBe 1
-
- # Disable it.
- atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])
- snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
- expect(Object.keys(snippets).length).toBe 0
-
- # Re-enable it.
- atom.config.set('core.packagesWithSnippetsDisabled', [])
- snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope'])
- expect(Object.keys(snippets).length).toBe 1
-
- atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)
diff --git a/spec/snippet-loading-spec.js b/spec/snippet-loading-spec.js
new file mode 100644
index 00000000..2781f13d
--- /dev/null
+++ b/spec/snippet-loading-spec.js
@@ -0,0 +1,310 @@
+const path = require('path');
+const fs = require('fs-plus');
+const temp = require('temp').track();
+
+describe("Snippet Loading", () => {
+ let configDirPath, snippetsService;
+
+ beforeEach(() => {
+ configDirPath = temp.mkdirSync('atom-config-dir-');
+ spyOn(atom, 'getConfigDirPath').andReturn(configDirPath);
+
+ spyOn(console, 'warn');
+ if (atom.notifications !== undefined) { spyOn(atom.notifications, 'addError'); }
+
+ spyOn(atom.packages, 'getLoadedPackages').andReturn([
+ atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-snippets')),
+ atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-broken-snippets')),
+ ]);
+ });
+
+ afterEach(() => {
+ waitsForPromise(() => Promise.resolve(atom.packages.deactivatePackages('snippets')));
+ runs(() => jasmine.unspy(atom.packages, 'getLoadedPackages'));
+ });
+
+ const activateSnippetsPackage = () => {
+ waitsForPromise(() => atom.packages.activatePackage("snippets").then(({mainModule}) => {
+ snippetsService = mainModule.provideSnippets();
+ mainModule.loaded = false;
+ }));
+ waitsFor("all snippets to load", 3000, () => snippetsService.bundledSnippetsLoaded());
+ };
+
+ it("loads the bundled snippet template snippets", () => {
+ activateSnippetsPackage();
+ runs(() => {
+ const jsonSnippet = snippetsService.snippetsForScopes(['.source.json'])['snip'];
+ expect(jsonSnippet.name).toBe('Atom Snippet');
+ expect(jsonSnippet.prefix).toBe('snip');
+ expect(jsonSnippet.body).toContain('"prefix":');
+ expect(jsonSnippet.body).toContain('"body":');
+ expect(jsonSnippet.tabStopList.length).toBeGreaterThan(0);
+
+ const csonSnippet = snippetsService.snippetsForScopes(['.source.coffee'])['snip'];
+ expect(csonSnippet.name).toBe('Atom Snippet');
+ expect(csonSnippet.prefix).toBe('snip');
+ expect(csonSnippet.body).toContain("'prefix':");
+ expect(csonSnippet.body).toContain("'body':");
+ expect(csonSnippet.tabStopList.length).toBeGreaterThan(0);
+ });
+ });
+
+ it("loads non-hidden snippet files from atom packages with snippets directories", () => {
+ activateSnippetsPackage();
+ runs(() => {
+ let snippet = snippetsService.snippetsForScopes(['.test'])['test'];
+ expect(snippet.prefix).toBe('test');
+ expect(snippet.body).toBe('testing 123');
+
+ snippet = snippetsService.snippetsForScopes(['.test'])['testd'];
+ expect(snippet.prefix).toBe('testd');
+ expect(snippet.body).toBe('testing 456');
+ expect(snippet.description).toBe('a description');
+ expect(snippet.descriptionMoreURL).toBe('http://google.com');
+
+ snippet = snippetsService.snippetsForScopes(['.test'])['testlabelleft'];
+ expect(snippet.prefix).toBe('testlabelleft');
+ expect(snippet.body).toBe('testing 456');
+ expect(snippet.leftLabel).toBe('a label');
+
+ snippet = snippetsService.snippetsForScopes(['.test'])['testhtmllabels'];
+ expect(snippet.prefix).toBe('testhtmllabels');
+ expect(snippet.body).toBe('testing 456');
+ expect(snippet.leftLabelHTML).toBe('Label');
+ expect(snippet.rightLabelHTML).toBe('Label');
+ });
+ });
+
+ it("logs a warning if package snippets files cannot be parsed", () => {
+ activateSnippetsPackage();
+ runs(() => {
+ // Warn about invalid-file, but don't even try to parse a hidden file
+ expect(console.warn.calls.length).toBeGreaterThan(0);
+ expect(console.warn.mostRecentCall.args[0]).toMatch(/Error reading.*package-with-broken-snippets/);
+ });
+ });
+
+ describe("::loadPackageSnippets(callback)", () => {
+ beforeEach(() => {// simulate a list of packages where the javascript core package is returned at the end
+ atom.packages.getLoadedPackages.andReturn([
+ atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-snippets')),
+ atom.packages.loadPackage('language-javascript')
+ ])
+ });
+
+ it("allows other packages to override core packages' snippets", () => {
+ waitsForPromise(() => atom.packages.activatePackage("language-javascript"));
+ activateSnippetsPackage();
+ runs(() => {
+ const snippet = snippetsService.snippetsForScopes(['.source.js'])['log'];
+ expect(snippet.body).toBe("from-a-community-package");
+ });
+ });
+ });
+
+ describe("::onDidLoadSnippets(callback)", () => {
+ it("invokes listeners when all snippets are loaded", () => {
+ let loadedCallback = null;
+ waitsFor("package to activate", done => {
+ atom.packages.activatePackage("snippets").then(({mainModule}) => {
+ loadedCallback = jasmine.createSpy('onDidLoadSnippets callback');
+ mainModule.onDidLoadSnippets(loadedCallback);
+ done();
+ });
+ });
+ waitsFor("onDidLoad callback to be called", () => loadedCallback.callCount > 0);
+ });
+ });
+
+ describe("when ~/.atom/snippets.json exists", () => {
+ beforeEach(() => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.json'), `\
+{
+ ".foo": {
+ "foo snippet": {
+ "prefix": "foo",
+ "body": "bar1"
+ }
+ }
+}\
+`
+ );
+ activateSnippetsPackage();
+ });
+
+ it("loads the snippets from that file", () => {
+ let snippet = null;
+ waitsFor(() => {
+ snippet = snippetsService.snippetsForScopes(['.foo'])['foo'];
+ return snippet;
+ });
+ runs(() => {
+ expect(snippet.name).toBe('foo snippet');
+ expect(snippet.prefix).toBe("foo");
+ expect(snippet.body).toBe("bar1");
+ });
+ });
+
+ describe("when that file changes", () => {
+ it("reloads the snippets", () => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.json'), `\
+{
+".foo": {
+ "foo snippet": {
+ "prefix": "foo",
+ "body": "bar2"
+ }
+}
+}\
+`
+ );
+
+ waitsFor("snippets to be changed", () => {
+ const snippet = snippetsService.snippetsForScopes(['.foo'])['foo'];
+ return snippet && snippet.body === 'bar2';
+ });
+
+ runs(() => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.json'), "");
+ });
+
+ waitsFor("snippets to be removed", () => !snippetsService.snippetsForScopes(['.foo'])['foo']);
+ });
+ });
+ });
+
+ describe("when ~/.atom/snippets.cson exists", () => {
+ beforeEach(() => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), `\
+".foo":
+ "foo snippet":
+ "prefix": "foo"
+ "body": "bar1"\
+`
+ );
+ return activateSnippetsPackage();
+ });
+
+ it("loads the snippets from that file", () => {
+ let snippet = null;
+ waitsFor(() => {
+ snippet = snippetsService.snippetsForScopes(['.foo'])['foo'];
+ return snippet;
+ });
+
+ runs(() => {
+ expect(snippet.name).toBe('foo snippet');
+ expect(snippet.prefix).toBe("foo");
+ expect(snippet.body).toBe("bar1");
+ });
+ });
+
+ describe("when that file changes", () => {
+ it("reloads the snippets", () => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), `\
+".foo":
+ "foo snippet":
+ "prefix": "foo"
+ "body": "bar2"\
+`
+ );
+
+ waitsFor("snippets to be changed", () => {
+ const snippet = snippetsService.snippetsForScopes(['.foo'])['foo'];
+ return snippet && snippet.body === 'bar2';
+ });
+
+ runs(() => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), "");
+ });
+
+ waitsFor("snippets to be removed", () => {
+ const snippet = snippetsService.snippetsForScopes(['.foo'])['foo'];
+ return !snippet;
+ });
+ });
+ });
+ });
+
+ it("notifies the user when the user snippets file cannot be loaded", () => {
+ fs.writeFileSync(path.join(configDirPath, 'snippets.cson'), `\
+".junk":::\
+`
+ );
+
+ activateSnippetsPackage();
+
+ runs(() => {
+ expect(console.warn).toHaveBeenCalled();
+ if (atom.notifications != null) {
+ expect(atom.notifications.addError).toHaveBeenCalled();
+ }
+ });
+ });
+
+ describe("packages-with-snippets-disabled feature", () => {
+ it("disables no snippets if the config option is empty", () => {
+ const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled');
+ atom.config.set('core.packagesWithSnippetsDisabled', []);
+
+ activateSnippetsPackage();
+ runs(() => {
+ const snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']);
+ expect(Object.keys(snippets).length).toBe(1);
+ atom.config.set('core.packagesWithSnippetsDisabled', originalConfig);
+ });
+ });
+
+ it("still includes a disabled package's snippets in the list of unparsed snippets", () => {
+ const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled');
+ atom.config.set('core.packagesWithSnippetsDisabled', []);
+
+ activateSnippetsPackage();
+ runs(() => {
+ atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']);
+ const allSnippets = snippetsService.getUnparsedSnippets();
+ const scopedSnippet = allSnippets.find(s => s.selectorString === '.package-with-snippets-unique-scope');
+ expect(scopedSnippet).not.toBe(undefined);
+
+ // NOTE: Original seems to have been incorrectly reassigning to originalConfig
+ atom.config.set('core.packagesWithSnippetsDisabled', originalConfig);
+ });
+ });
+
+ it("never loads a package's snippets when that package is disabled in config", () => {
+ const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled');
+ atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']);
+
+ activateSnippetsPackage();
+ runs(() => {
+ const snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']);
+ expect(Object.keys(snippets).length).toBe(0);
+ atom.config.set('core.packagesWithSnippetsDisabled', originalConfig);
+ });
+ });
+
+ it("unloads and/or reloads snippets from a package if the config option is changed after activation", () => {
+ const originalConfig = atom.config.get('core.packagesWithSnippetsDisabled');
+ atom.config.set('core.packagesWithSnippetsDisabled', []);
+
+ activateSnippetsPackage();
+ runs(() => {
+ let snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']);
+ expect(Object.keys(snippets).length).toBe(1);
+
+ // Disable it.
+ atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets']);
+ snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']);
+ expect(Object.keys(snippets).length).toBe(0);
+
+ // Re-enable it.
+ atom.config.set('core.packagesWithSnippetsDisabled', []);
+ snippets = snippetsService.snippetsForScopes(['.package-with-snippets-unique-scope']);
+ expect(Object.keys(snippets).length).toBe(1);
+
+ atom.config.set('core.packagesWithSnippetsDisabled', originalConfig);
+ });
+ });
+ });
+});
diff --git a/spec/snippets-spec.coffee b/spec/snippets-spec.coffee
deleted file mode 100644
index dd00a5d1..00000000
--- a/spec/snippets-spec.coffee
+++ /dev/null
@@ -1,1072 +0,0 @@
-path = require 'path'
-temp = require('temp').track()
-Snippets = require '../lib/snippets'
-{TextEditor} = require 'atom'
-
-describe "Snippets extension", ->
- [editorElement, editor] = []
-
- simulateTabKeyEvent = ({shift}={}) ->
- event = atom.keymaps.constructor.buildKeydownEvent('tab', {shift, target: editorElement})
- atom.keymaps.handleKeyboardEvent(event)
-
- beforeEach ->
- spyOn(Snippets, 'loadAll')
- spyOn(Snippets, 'getUserSnippetsPath').andReturn('')
-
- waitsForPromise ->
- atom.workspace.open('sample.js')
-
- waitsForPromise ->
- atom.packages.activatePackage('language-javascript')
-
- waitsForPromise ->
- atom.packages.activatePackage('snippets')
-
- runs ->
- editor = atom.workspace.getActiveTextEditor()
- editorElement = atom.views.getView(editor)
-
- afterEach ->
- waitsForPromise ->
- atom.packages.deactivatePackage('snippets')
-
- describe "provideSnippets interface", ->
- snippetsInterface = null
-
- beforeEach ->
- snippetsInterface = Snippets.provideSnippets()
-
- describe "bundledSnippetsLoaded", ->
- it "indicates the loaded state of the bundled snippets", ->
- expect(snippetsInterface.bundledSnippetsLoaded()).toBe false
- Snippets.doneLoading()
- expect(snippetsInterface.bundledSnippetsLoaded()).toBe true
-
- it "resets the loaded state after snippets is deactivated", ->
- expect(snippetsInterface.bundledSnippetsLoaded()).toBe false
- Snippets.doneLoading()
- expect(snippetsInterface.bundledSnippetsLoaded()).toBe true
-
- waitsForPromise -> atom.packages.deactivatePackage('snippets')
- waitsForPromise -> atom.packages.activatePackage('snippets')
-
- runs ->
- expect(snippetsInterface.bundledSnippetsLoaded()).toBe false
- Snippets.doneLoading()
- expect(snippetsInterface.bundledSnippetsLoaded()).toBe true
-
- describe "insertSnippet", ->
- it "can insert a snippet", ->
- editor.setSelectedBufferRange([[0, 4], [0, 13]])
- snippetsInterface.insertSnippet("hello ${1:world}", editor)
- expect(editor.lineTextForBufferRow(0)).toBe "var hello world = function () {"
-
- it "returns false for snippetToExpandUnderCursor if getSnippets returns {}", ->
- snippets = atom.packages.getActivePackage('snippets').mainModule
- expect(snippets.snippetToExpandUnderCursor(editor)).toEqual false
-
- it "ignores invalid snippets in the config", ->
- snippets = atom.packages.getActivePackage('snippets').mainModule
-
- invalidSnippets = null
- spyOn(snippets.scopedPropertyStore, 'getPropertyValue').andCallFake -> invalidSnippets
- expect(snippets.getSnippets(editor)).toEqual {}
-
- invalidSnippets = 'test'
- expect(snippets.getSnippets(editor)).toEqual {}
-
- invalidSnippets = []
- expect(snippets.getSnippets(editor)).toEqual {}
-
- invalidSnippets = 3
- expect(snippets.getSnippets(editor)).toEqual {}
-
- invalidSnippets = {a: null}
- expect(snippets.getSnippets(editor)).toEqual {}
-
- describe "when null snippets are present", ->
- beforeEach ->
- Snippets.add __filename,
- '.source.js':
- "some snippet":
- prefix: "t1"
- body: "this is a test"
-
- '.source.js .nope':
- "some snippet":
- prefix: "t1"
- body: null
-
- it "overrides the less-specific defined snippet", ->
- snippets = Snippets.provideSnippets()
- expect(snippets.snippetsForScopes(['.source.js'])['t1']).toBeTruthy()
- expect(snippets.snippetsForScopes(['.source.js .nope.not-today'])['t1']).toBeFalsy()
-
- describe "when 'tab' is triggered on the editor", ->
- beforeEach ->
- Snippets.add __filename,
- ".source.js":
- "without tab stops":
- prefix: "t1"
- body: "this is a test"
-
- "with only an end tab stop":
- prefix: "t1a"
- body: "something $0 strange"
-
- "overlapping prefix":
- prefix: "tt1"
- body: "this is another test"
-
- "special chars":
- prefix: "@unique"
- body: "@unique see"
-
- "tab stops":
- prefix: "t2"
- body: """
- go here next:($2) and finally go here:($0)
- go here first:($1)
-
- """
-
- "indented second line":
- prefix: "t3"
- body: """
- line 1
- \tline 2$1
- $2
- """
-
- "multiline with indented placeholder tabstop":
- prefix: "t4"
- body: """
- line ${1:1}
- ${2:body...}
- """
-
- "multiline starting with tabstop":
- prefix: "t4b"
- body: """
- $1 = line 1 {
- line 2
- }
- """
-
- "nested tab stops":
- prefix: "t5"
- body: '${1:"${2:key}"}: ${3:value}'
-
- "caused problems with undo":
- prefix: "t6"
- body: """
- first line$1
- ${2:placeholder ending second line}
- """
-
- "tab stops at beginning and then end of snippet":
- prefix: "t6b"
- body: "$1expanded$0"
-
- "tab stops at end and then beginning of snippet":
- prefix: "t6c"
- body: "$0expanded$1"
-
- "contains empty lines":
- prefix: "t7"
- body: """
- first line $1
-
-
- fourth line after blanks $2
- """
- "with/without placeholder":
- prefix: "t8"
- body: """
- with placeholder ${1:test}
- without placeholder ${2}
- """
-
- "multi-caret":
- prefix: "t9"
- body: """
- with placeholder ${1:test}
- without placeholder $1
- """
-
- "multi-caret-multi-tabstop":
- prefix: "t9b"
- body: """
- with placeholder ${1:test}
- without placeholder $1
- second tabstop $2
- third tabstop $3
- """
-
- "large indices":
- prefix: "t10"
- body: """
- hello${10} ${11:large} indices${1}
- """
-
- "no body":
- prefix: "bad1"
-
- "number body":
- prefix: "bad2"
- body: 100
-
- "many tabstops":
- prefix: "t11"
- body: """
- $0one${1} ${2:two} three${3}
- """
-
- "simple transform":
- prefix: "t12"
- body: """
- [${1:b}][/${1/[ ]+.*$//}]
- """
- "transform with non-transforming mirrors":
- prefix: "t13"
- body: """
- ${1:placeholder}\n${1/(.)/\\u$1/}\n$1
- """
- "multiple tab stops, some with transforms and some without":
- prefix: "t14"
- body: """
- ${1:placeholder} ${1/(.)/\\u$1/} $1 ${2:ANOTHER} ${2/^(.*)$/\\L$1/} $2
- """
- "has a transformed tab stop without a corresponding ordinary tab stop":
- prefix: 't15'
- body: """
- ${1/(.)/\\u$1/} & $2
- """
- "has a transformed tab stop that occurs before the corresponding ordinary tab stop":
- prefix: 't16'
- body: """
- & ${1/(.)/\\u$1/} & ${1:q}
- """
- "has a placeholder that mirrors another tab stop's content":
- prefix: 't17'
- body: "$4console.${3:log}('${2:uh $1}', $1);$0"
- "has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step":
- prefix: 't18'
- body: '// $1\n// ${1/./=/}'
-
- it "parses snippets once, reusing cached ones on subsequent queries", ->
- spyOn(Snippets, "getBodyParser").andCallThrough()
-
- editor.insertText("t1")
- simulateTabKeyEvent()
-
- expect(Snippets.getBodyParser).toHaveBeenCalled()
- expect(editor.lineTextForBufferRow(0)).toBe "this is a testvar quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 14]
-
- Snippets.getBodyParser.reset()
-
- editor.setText("")
- editor.insertText("t1")
- simulateTabKeyEvent()
-
- expect(Snippets.getBodyParser).not.toHaveBeenCalled()
- expect(editor.lineTextForBufferRow(0)).toBe "this is a test"
- expect(editor.getCursorScreenPosition()).toEqual [0, 14]
-
- Snippets.getBodyParser.reset()
-
- Snippets.add __filename,
- ".source.js":
- "invalidate previous snippet":
- prefix: "t1"
- body: "new snippet"
-
- editor.setText("")
- editor.insertText("t1")
- simulateTabKeyEvent()
-
- expect(Snippets.getBodyParser).toHaveBeenCalled()
- expect(editor.lineTextForBufferRow(0)).toBe "new snippet"
- expect(editor.getCursorScreenPosition()).toEqual [0, 11]
-
- describe "when the snippet body is invalid or missing", ->
- it "does not register the snippet", ->
- editor.setText('')
- editor.insertText('bad1')
- atom.commands.dispatch editorElement, 'snippets:expand'
- expect(editor.getText()).toBe 'bad1'
-
- editor.setText('')
- editor.setText('bad2')
- atom.commands.dispatch editorElement, 'snippets:expand'
- expect(editor.getText()).toBe 'bad2'
-
- describe "when the letters preceding the cursor trigger a snippet", ->
- describe "when the snippet contains no tab stops", ->
- it "replaces the prefix with the snippet text and places the cursor at its end", ->
- editor.insertText("t1")
- expect(editor.getCursorScreenPosition()).toEqual [0, 2]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "this is a testvar quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 14]
-
- it "inserts a real tab the next time a tab is pressed after the snippet is expanded", ->
- editor.insertText("t1")
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "this is a testvar quicksort = function () {"
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "this is a test var quicksort = function () {"
-
- describe "when the snippet contains tab stops", ->
- it "places the cursor at the first tab-stop, and moves the cursor in response to 'next-tab-stop' events", ->
- markerCountBefore = editor.getMarkerCount()
- editor.setCursorScreenPosition([2, 0])
- editor.insertText('t2')
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(2)).toBe "go here next:() and finally go here:()"
- expect(editor.lineTextForBufferRow(3)).toBe "go here first:()"
- expect(editor.lineTextForBufferRow(4)).toBe " if (items.length <= 1) return items;"
- expect(editor.getSelectedBufferRange()).toEqual [[3, 15], [3, 15]]
-
- simulateTabKeyEvent()
- expect(editor.getSelectedBufferRange()).toEqual [[2, 14], [2, 14]]
- editor.insertText 'abc'
-
- simulateTabKeyEvent()
- expect(editor.getSelectedBufferRange()).toEqual [[2, 40], [2, 40]]
-
- # tab backwards
- simulateTabKeyEvent(shift: true)
- expect(editor.getSelectedBufferRange()).toEqual [[2, 14], [2, 17]] # should highlight text typed at tab stop
-
- simulateTabKeyEvent(shift: true)
- expect(editor.getSelectedBufferRange()).toEqual [[3, 15], [3, 15]]
-
- # shift-tab on first tab-stop does nothing
- simulateTabKeyEvent(shift: true)
- expect(editor.getCursorScreenPosition()).toEqual [3, 15]
-
- # tab through all tab stops, then tab on last stop to terminate snippet
- simulateTabKeyEvent()
- simulateTabKeyEvent()
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(2)).toBe "go here next:(abc) and finally go here:( )"
- expect(editor.getMarkerCount()).toBe markerCountBefore
-
- describe "when tab stops are nested", ->
- it "destroys the inner tab stop if the outer tab stop is modified", ->
- editor.setText('')
- editor.insertText 't5'
- atom.commands.dispatch editorElement, 'snippets:expand'
- expect(editor.lineTextForBufferRow(0)).toBe '"key": value'
- expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 5]]
- editor.insertText("foo")
- simulateTabKeyEvent()
- expect(editor.getSelectedBufferRange()).toEqual [[0, 5], [0, 10]]
-
- describe "when the only tab stop is an end stop", ->
- it "terminates the snippet immediately after moving the cursor to the end stop", ->
- editor.setText('')
- editor.insertText 't1a'
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(0)).toBe "something strange"
- expect(editor.getCursorBufferPosition()).toEqual [0, 10]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "something strange"
- expect(editor.getCursorBufferPosition()).toEqual [0, 12]
-
- describe "when tab stops are separated by blank lines", ->
- it "correctly places the tab stops (regression)", ->
- editor.setText('')
- editor.insertText 't7'
- atom.commands.dispatch editorElement, 'snippets:expand'
- atom.commands.dispatch editorElement, 'snippets:next-tab-stop'
- expect(editor.getCursorBufferPosition()).toEqual [3, 25]
-
- describe "when the cursor is moved beyond the bounds of the current tab stop", ->
- it "terminates the snippet", ->
- editor.setCursorScreenPosition([2, 0])
- editor.insertText('t2')
- simulateTabKeyEvent()
-
- editor.moveUp()
- editor.moveLeft()
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(2)).toBe "go here next:( ) and finally go here:()"
- expect(editor.getCursorBufferPosition()).toEqual [2, 16]
-
- # test we can terminate with shift-tab
- editor.setCursorScreenPosition([4, 0])
- editor.insertText('t2')
- simulateTabKeyEvent()
- simulateTabKeyEvent()
-
- editor.moveRight()
- simulateTabKeyEvent(shift: true)
- expect(editor.getCursorBufferPosition()).toEqual [4, 15]
-
- describe "when the cursor is moved within the bounds of the current tab stop", ->
- it "should not terminate the snippet", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t8')
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder test"
- editor.moveRight()
- editor.moveLeft()
- editor.insertText("foo")
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder tesfoot"
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder var quicksort = function () {"
- editor.insertText("test")
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder testvar quicksort = function () {"
- editor.moveLeft()
- editor.insertText("foo")
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder tesfootvar quicksort = function () {"
-
- describe "when the backspace is press within the bounds of the current tab stop", ->
- it "should not terminate the snippet", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t8')
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder test"
- editor.moveRight()
- editor.backspace()
- editor.insertText("foo")
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder tesfoo"
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder var quicksort = function () {"
- editor.insertText("test")
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder testvar quicksort = function () {"
- editor.backspace()
- editor.insertText("foo")
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder tesfoovar quicksort = function () {"
-
- describe "when the snippet contains hard tabs", ->
- describe "when the edit session is in soft-tabs mode", ->
- it "translates hard tabs in the snippet to the appropriate number of spaces", ->
- expect(editor.getSoftTabs()).toBeTruthy()
- editor.insertText("t3")
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(1)).toBe " line 2"
- expect(editor.getCursorBufferPosition()).toEqual [1, 8]
-
- describe "when the edit session is in hard-tabs mode", ->
- it "inserts hard tabs in the snippet directly", ->
- editor.setSoftTabs(false)
- editor.insertText("t3")
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(1)).toBe "\tline 2"
- expect(editor.getCursorBufferPosition()).toEqual [1, 7]
-
- describe "when the snippet prefix is indented", ->
- describe "when the snippet spans a single line", ->
- it "does not indent the next line", ->
- editor.setCursorScreenPosition([2, Infinity])
- editor.insertText ' t1'
- atom.commands.dispatch editorElement, 'snippets:expand'
- expect(editor.lineTextForBufferRow(3)).toBe " var pivot = items.shift(), current, left = [], right = [];"
-
- describe "when the snippet spans multiple lines", ->
- it "indents the subsequent lines of the snippet to be even with the start of the first line", ->
- expect(editor.getSoftTabs()).toBeTruthy()
- editor.setCursorScreenPosition([2, Infinity])
- editor.insertText ' t3'
- atom.commands.dispatch editorElement, 'snippets:expand'
- expect(editor.lineTextForBufferRow(2)).toBe " if (items.length <= 1) return items; line 1"
- expect(editor.lineTextForBufferRow(3)).toBe " line 2"
- expect(editor.getCursorBufferPosition()).toEqual [3, 12]
-
- describe "when the snippet spans multiple lines", ->
- beforeEach ->
- editor.update({autoIndent: true})
- # editor.update() returns a Promise that never gets resolved, so we
- # need to return undefined to avoid a timeout in the spec.
- # TODO: Figure out why `editor.update({autoIndent: true})` never gets resolved.
- return
-
- it "places tab stops correctly", ->
- expect(editor.getSoftTabs()).toBeTruthy()
- editor.setCursorScreenPosition([2, Infinity])
- editor.insertText ' t3'
- atom.commands.dispatch editorElement, 'snippets:expand'
- expect(editor.getCursorBufferPosition()).toEqual [3, 12]
- atom.commands.dispatch editorElement, 'snippets:next-tab-stop'
- expect(editor.getCursorBufferPosition()).toEqual [4, 4]
-
- it "indents the subsequent lines of the snippet based on the indent level before the snippet is inserted", ->
- editor.setCursorScreenPosition([2, Infinity])
- editor.insertNewline()
- editor.insertText 't4b'
- atom.commands.dispatch editorElement, 'snippets:expand'
-
- expect(editor.lineTextForBufferRow(3)).toBe " = line 1 {" # 4 + 1 spaces (because the tab stop is invisible)
- expect(editor.lineTextForBufferRow(4)).toBe " line 2"
- expect(editor.lineTextForBufferRow(5)).toBe " }"
- expect(editor.getCursorBufferPosition()).toEqual [3, 4]
-
- it "does not change the relative positioning of the tab stops when inserted multiple times", ->
- editor.setCursorScreenPosition([2, Infinity])
- editor.insertNewline()
- editor.insertText 't4'
- atom.commands.dispatch editorElement, 'snippets:expand'
-
- expect(editor.getSelectedBufferRange()).toEqual [[3, 9], [3, 10]]
- atom.commands.dispatch editorElement, 'snippets:next-tab-stop'
- expect(editor.getSelectedBufferRange()).toEqual [[4, 6], [4, 13]]
-
- editor.insertText 't4'
- atom.commands.dispatch editorElement, 'snippets:expand'
-
- expect(editor.getSelectedBufferRange()).toEqual [[4, 11], [4, 12]]
- atom.commands.dispatch editorElement, 'snippets:next-tab-stop'
- expect(editor.getSelectedBufferRange()).toEqual [[5, 8], [5, 15]]
-
- editor.setText('') # Clear editor
- editor.insertText 't4'
- atom.commands.dispatch editorElement, 'snippets:expand'
-
- expect(editor.getSelectedBufferRange()).toEqual [[0, 5], [0, 6]]
- atom.commands.dispatch editorElement, 'snippets:next-tab-stop'
- expect(editor.getSelectedBufferRange()).toEqual [[1, 2], [1, 9]]
-
- describe "when multiple snippets match the prefix", ->
- it "expands the snippet that is the longest match for the prefix", ->
- editor.insertText('t113')
- expect(editor.getCursorScreenPosition()).toEqual [0, 4]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "t113 var quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 6]
-
- editor.undo()
- editor.undo()
-
- editor.insertText("tt1")
- expect(editor.getCursorScreenPosition()).toEqual [0, 3]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "this is another testvar quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 20]
-
- editor.undo()
- editor.undo()
-
- editor.insertText("@t1")
- expect(editor.getCursorScreenPosition()).toEqual [0, 3]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "@this is a testvar quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 15]
-
- describe "when the word preceding the cursor ends with a snippet prefix", ->
- it "inserts a tab as normal", ->
- editor.insertText("t1t1t1")
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "t1t1t1 var quicksort = function () {"
-
- describe "when the letters preceding the cursor don't match a snippet", ->
- it "inserts a tab as normal", ->
- editor.insertText("xxte")
- expect(editor.getCursorScreenPosition()).toEqual [0, 4]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "xxte var quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 6]
-
- describe "when text is selected", ->
- it "inserts a tab as normal", ->
- editor.insertText("t1")
- editor.setSelectedBufferRange([[0, 0], [0, 2]])
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe " t1var quicksort = function () {"
- expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 4]]
-
- describe "when a previous snippet expansion has just been undone", ->
- describe "when the tab stops appear in the middle of the snippet", ->
- it "expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", ->
- editor.insertText 't6\n'
- editor.setCursorBufferPosition [0, 2]
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "first line"
- editor.undo()
- expect(editor.lineTextForBufferRow(0)).toBe "t6"
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "first line"
-
- describe "when the tab stops appear at the beginning and then the end of snippet", ->
- it "expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", ->
- editor.insertText 't6b\n'
- editor.setCursorBufferPosition [0, 3]
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "expanded"
- editor.undo()
- expect(editor.lineTextForBufferRow(0)).toBe "t6b"
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "expanded"
- expect(editor.getCursorBufferPosition()).toEqual([0, 0])
-
- describe "when the tab stops appear at the end and then the beginning of snippet", ->
- it "expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", ->
- editor.insertText 't6c\n'
- editor.setCursorBufferPosition [0, 3]
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "expanded"
- editor.undo()
- expect(editor.lineTextForBufferRow(0)).toBe "t6c"
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "expanded"
- expect(editor.getCursorBufferPosition()).toEqual([0, 8])
-
- describe "when the prefix contains non-word characters", ->
- it "selects the non-word characters as part of the prefix", ->
- editor.insertText("@unique")
- expect(editor.getCursorScreenPosition()).toEqual [0, 7]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "@unique seevar quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 11]
-
- editor.setCursorBufferPosition [10, 0]
- editor.insertText("'@unique")
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(10)).toBe "'@unique see"
- expect(editor.getCursorScreenPosition()).toEqual [10, 12]
-
- it "does not select the whitespace before the prefix", ->
- editor.insertText("a; @unique")
- expect(editor.getCursorScreenPosition()).toEqual [0, 10]
-
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "a; @unique seevar quicksort = function () {"
- expect(editor.getCursorScreenPosition()).toEqual [0, 14]
-
- describe "when snippet contains tabstops with or without placeholder", ->
- it "should create two markers", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t8')
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder test"
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder var quicksort = function () {"
-
- expect(editor.getSelectedBufferRange()).toEqual [[0, 17], [0, 21]]
-
- simulateTabKeyEvent()
- expect(editor.getSelectedBufferRange()).toEqual [[1, 20], [1, 20]]
-
- describe "when snippet contains multi-caret tabstops with or without placeholder", ->
- it "should create two markers", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t9')
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder test"
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder var quicksort = function () {"
- editor.insertText('hello')
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder hello"
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder hellovar quicksort = function () {"
-
- it "terminates the snippet when cursors are destroyed", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t9b')
- simulateTabKeyEvent()
- editor.getCursors()[0].destroy()
- editor.getCursorBufferPosition()
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(1)).toEqual("without placeholder ")
-
- it "terminates the snippet expansion if a new cursor moves outside the bounds of the tab stops", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t9b')
- simulateTabKeyEvent()
- editor.insertText('test')
-
- editor.getCursors()[0].destroy()
- editor.moveDown() # this should destroy the previous expansion
- editor.moveToBeginningOfLine()
-
- # this should insert whitespace instead of going through tabstops of the previous destroyed snippet
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(2).indexOf(" second")).toBe 0
-
- it "moves to the second tabstop after a multi-caret tabstop", ->
- editor.setCursorScreenPosition([0, 0])
- editor.insertText('t9b')
- simulateTabKeyEvent()
- editor.insertText('line 1')
-
- simulateTabKeyEvent()
- editor.insertText('line 2')
-
- simulateTabKeyEvent()
- editor.insertText('line 3')
-
- expect(editor.lineTextForBufferRow(2).indexOf("line 2 ")).toBe -1
-
- it "mirrors input properly when a tabstop's placeholder refers to another tabstop", ->
- editor.setText('t17')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- editor.insertText("foo")
- expect(editor.getText()).toBe "console.log('uh foo', foo);"
- simulateTabKeyEvent()
- editor.insertText("bar")
- expect(editor.getText()).toBe "console.log('bar', foo);"
-
- describe "when the snippet contains tab stops with transformations", ->
- it "transforms the text typed into the first tab stop before setting it in the transformed tab stop", ->
- editor.setText('t12')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- expect(editor.getText()).toBe("[b][/b]")
- editor.insertText('img src')
- expect(editor.getText()).toBe("[img src][/img]")
-
- it "bundles the transform mutations along with the original manual mutation for the purposes of undo and redo", ->
- editor.setText('t12')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- editor.insertText('i')
- expect(editor.getText()).toBe("[i][/i]")
-
- editor.insertText('mg src')
- expect(editor.getText()).toBe("[img src][/img]")
-
- editor.undo()
- expect(editor.getText()).toBe("[i][/i]")
-
- editor.redo()
- expect(editor.getText()).toBe("[img src][/img]")
-
- it "can pick the right insertion to use as the primary even if a transformed insertion occurs first in the snippet", ->
- editor.setText('t16')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe("& Q & q")
- expect(editor.getCursorBufferPosition()).toEqual([0, 7])
-
- editor.insertText('rst')
- expect(editor.lineTextForBufferRow(0)).toBe("& RST & rst")
-
- it "silently ignores a tab stop without a non-transformed insertion to use as the primary", ->
- editor.setText('t15')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- editor.insertText('a')
- expect(editor.lineTextForBufferRow(0)).toBe(" & a")
- expect(editor.getCursorBufferPosition()).toEqual([0, 4])
-
- describe "when the snippet contains mirrored tab stops and tab stops with transformations", ->
- it "adds cursors for the mirrors but not the transformations", ->
- editor.setText('t13')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- expect(editor.getCursors().length).toBe(2)
- expect(editor.getText()).toBe """
- placeholder
- PLACEHOLDER
-
- """
-
- editor.insertText('foo')
-
- expect(editor.getText()).toBe """
- foo
- FOO
- foo
- """
-
- describe "when the snippet contains multiple tab stops, some with transformations and some without", ->
- it "does not get confused", ->
- editor.setText('t14')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- expect(editor.getCursors().length).toBe(2)
- expect(editor.getText()).toBe "placeholder PLACEHOLDER ANOTHER another "
- simulateTabKeyEvent()
- expect(editor.getCursors().length).toBe(2)
- editor.insertText('FOO')
- expect(editor.getText()).toBe """
- placeholder PLACEHOLDER FOO foo FOO
- """
-
- describe "when the snippet has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step", ->
- it "terminates the snippet upon such a cursor move", ->
- editor.setText('t18')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- expect(editor.getText()).toBe("// \n// ")
- expect(editor.getCursorBufferPosition()).toEqual [0, 3]
- editor.insertText('wat')
- expect(editor.getText()).toBe("// wat\n// ===")
- # Move the cursor down one line, then up one line. This puts the cursor
- # back in its previous position, but the snippet should no longer be
- # active, so when we type more text, it should not be mirrored.
- editor.setCursorScreenPosition([1, 6])
- editor.setCursorScreenPosition([0, 6])
- editor.insertText('wat')
- expect(editor.getText()).toBe("// watwat\n// ===")
-
-
- describe "when the snippet contains tab stops with an index >= 10", ->
- it "parses and orders the indices correctly", ->
- editor.setText('t10')
- editor.setCursorScreenPosition([0, 3])
- simulateTabKeyEvent()
- expect(editor.getText()).toBe "hello large indices"
- expect(editor.getCursorBufferPosition()).toEqual [0, 19]
- simulateTabKeyEvent()
- expect(editor.getCursorBufferPosition()).toEqual [0, 5]
- simulateTabKeyEvent()
- expect(editor.getSelectedBufferRange()).toEqual [[0, 6], [0, 11]]
-
- describe "when there are multiple cursors", ->
- describe "when the cursors share a common snippet prefix", ->
- it "expands the snippet for all cursors and allows simultaneous editing", ->
- editor.insertText('t9')
- editor.setCursorBufferPosition([12, 2])
- editor.insertText(' t9')
- editor.addCursorAtBufferPosition([0, 2])
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder test"
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder var quicksort = function () {"
- expect(editor.lineTextForBufferRow(13)).toBe "}; with placeholder test"
- expect(editor.lineTextForBufferRow(14)).toBe "without placeholder "
-
- editor.insertText('hello')
- expect(editor.lineTextForBufferRow(0)).toBe "with placeholder hello"
- expect(editor.lineTextForBufferRow(1)).toBe "without placeholder hellovar quicksort = function () {"
- expect(editor.lineTextForBufferRow(13)).toBe "}; with placeholder hello"
- expect(editor.lineTextForBufferRow(14)).toBe "without placeholder hello"
-
- it "applies transformations identically to single-expansion mode", ->
- editor.setText('t14\nt14')
- editor.setCursorBufferPosition([1, 3])
- editor.addCursorAtBufferPosition([0, 3])
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(0)).toBe "placeholder PLACEHOLDER ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "placeholder PLACEHOLDER ANOTHER another "
-
- editor.insertText "testing"
-
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing ANOTHER another "
-
- simulateTabKeyEvent()
- editor.insertText "AGAIN"
-
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing AGAIN again AGAIN"
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing AGAIN again AGAIN"
-
- it "bundles transform-induced mutations into a single history entry along with their triggering edit, even across multiple snippets", ->
- editor.setText('t14\nt14')
- editor.setCursorBufferPosition([1, 3])
- editor.addCursorAtBufferPosition([0, 3])
- simulateTabKeyEvent()
-
- expect(editor.lineTextForBufferRow(0)).toBe "placeholder PLACEHOLDER ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "placeholder PLACEHOLDER ANOTHER another "
-
- editor.insertText "testing"
-
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing ANOTHER another "
-
- simulateTabKeyEvent()
- editor.insertText "AGAIN"
-
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing AGAIN again AGAIN"
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing AGAIN again AGAIN"
-
- editor.undo()
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing ANOTHER another "
-
- editor.undo()
- expect(editor.lineTextForBufferRow(0)).toBe "placeholder PLACEHOLDER ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "placeholder PLACEHOLDER ANOTHER another "
-
- editor.redo()
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing ANOTHER another "
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing ANOTHER another "
-
- editor.redo()
- expect(editor.lineTextForBufferRow(0)).toBe "testing TESTING testing AGAIN again AGAIN"
- expect(editor.lineTextForBufferRow(1)).toBe "testing TESTING testing AGAIN again AGAIN"
-
- describe "when there are many tabstops", ->
- it "moves the cursors between the tab stops for their corresponding snippet when tab and shift-tab are pressed", ->
- editor.addCursorAtBufferPosition([7, 5])
- editor.addCursorAtBufferPosition([12, 2])
- editor.insertText('t11')
- simulateTabKeyEvent()
-
- cursors = editor.getCursors()
- expect(cursors.length).toEqual 3
-
- expect(cursors[0].getBufferPosition()).toEqual [0, 3]
- expect(cursors[1].getBufferPosition()).toEqual [7, 8]
- expect(cursors[2].getBufferPosition()).toEqual [12, 5]
- expect(cursors[0].selection.isEmpty()).toBe true
- expect(cursors[1].selection.isEmpty()).toBe true
- expect(cursors[2].selection.isEmpty()).toBe true
-
- simulateTabKeyEvent()
- expect(cursors[0].getBufferPosition()).toEqual [0, 7]
- expect(cursors[1].getBufferPosition()).toEqual [7, 12]
- expect(cursors[2].getBufferPosition()).toEqual [12, 9]
- expect(cursors[0].selection.isEmpty()).toBe false
- expect(cursors[1].selection.isEmpty()).toBe false
- expect(cursors[2].selection.isEmpty()).toBe false
- expect(cursors[0].selection.getText()).toEqual 'two'
- expect(cursors[1].selection.getText()).toEqual 'two'
- expect(cursors[2].selection.getText()).toEqual 'two'
-
- simulateTabKeyEvent()
- expect(cursors[0].getBufferPosition()).toEqual [0, 13]
- expect(cursors[1].getBufferPosition()).toEqual [7, 18]
- expect(cursors[2].getBufferPosition()).toEqual [12, 15]
- expect(cursors[0].selection.isEmpty()).toBe true
- expect(cursors[1].selection.isEmpty()).toBe true
- expect(cursors[2].selection.isEmpty()).toBe true
-
- simulateTabKeyEvent()
- expect(cursors[0].getBufferPosition()).toEqual [0, 0]
- expect(cursors[1].getBufferPosition()).toEqual [7, 5]
- expect(cursors[2].getBufferPosition()).toEqual [12, 2]
- expect(cursors[0].selection.isEmpty()).toBe true
- expect(cursors[1].selection.isEmpty()).toBe true
- expect(cursors[2].selection.isEmpty()).toBe true
-
- describe "when the cursors do not share common snippet prefixes", ->
- it "inserts tabs as normal", ->
- editor.insertText('t9')
- editor.setCursorBufferPosition([12, 2])
- editor.insertText(' t8')
- editor.addCursorAtBufferPosition([0, 2])
- simulateTabKeyEvent()
- expect(editor.lineTextForBufferRow(0)).toBe "t9 var quicksort = function () {"
- expect(editor.lineTextForBufferRow(12)).toBe "}; t8 "
-
- describe "when a snippet is triggered within an existing snippet expansion", ->
- it "ignores the snippet expansion and goes to the next tab stop", ->
- editor.addCursorAtBufferPosition([7, 5])
- editor.addCursorAtBufferPosition([12, 2])
- editor.insertText('t11')
- simulateTabKeyEvent()
- simulateTabKeyEvent()
-
- editor.insertText('t1')
- simulateTabKeyEvent()
-
- cursors = editor.getCursors()
- expect(cursors.length).toEqual 3
-
- expect(cursors[0].getBufferPosition()).toEqual [0, 12]
- expect(cursors[1].getBufferPosition()).toEqual [7, 17]
- expect(cursors[2].getBufferPosition()).toEqual [12, 14]
- expect(cursors[0].selection.isEmpty()).toBe true
- expect(cursors[1].selection.isEmpty()).toBe true
- expect(cursors[2].selection.isEmpty()).toBe true
- expect(editor.lineTextForBufferRow(0)).toBe "one t1 threevar quicksort = function () {"
- expect(editor.lineTextForBufferRow(7)).toBe " }one t1 three"
- expect(editor.lineTextForBufferRow(12)).toBe "};one t1 three"
-
- describe "when the editor is not a pane item (regression)", ->
- it "handles tab stops correctly", ->
- editor = new TextEditor()
- atom.grammars.assignLanguageMode(editor, 'source.js')
- editorElement = editor.getElement()
-
- editor.insertText('t2')
- simulateTabKeyEvent()
- editor.insertText('ABC')
- expect(editor.getText()).toContain('go here first:(ABC)')
-
- editor.undo()
- editor.undo()
- expect(editor.getText()).toBe('t2')
- simulateTabKeyEvent()
- editor.insertText('ABC')
- expect(editor.getText()).toContain('go here first:(ABC)')
-
- describe "when atom://.atom/snippets is opened", ->
- it "opens ~/.atom/snippets.cson", ->
- jasmine.unspy(Snippets, 'getUserSnippetsPath')
- atom.workspace.destroyActivePaneItem()
- configDirPath = temp.mkdirSync('atom-config-dir-')
- spyOn(atom, 'getConfigDirPath').andReturn configDirPath
- atom.workspace.open('atom://.atom/snippets')
-
- waitsFor ->
- atom.workspace.getActiveTextEditor()?
-
- runs ->
- expect(atom.workspace.getActiveTextEditor().getURI()).toBe path.join(configDirPath, 'snippets.cson')
-
- describe "snippet insertion API", ->
- it "will automatically parse snippet definition and replace selection", ->
- editor.setSelectedBufferRange([[0, 4], [0, 13]])
- Snippets.insert("hello ${1:world}", editor)
-
- expect(editor.lineTextForBufferRow(0)).toBe "var hello world = function () {"
- expect(editor.getSelectedBufferRange()).toEqual [[0, 10], [0, 15]]
-
- describe "when the 'snippets:available' command is triggered", ->
- availableSnippetsView = null
-
- beforeEach ->
- Snippets.add __filename,
- ".source.js":
- "test":
- prefix: "test"
- body: "${1:Test pass you will}, young "
-
- "challenge":
- prefix: "chal"
- body: "$1: ${2:To pass this challenge}"
-
- delete Snippets.availableSnippetsView
-
- atom.commands.dispatch(editorElement, "snippets:available")
-
- waitsFor ->
- atom.workspace.getModalPanels().length is 1
-
- runs ->
- availableSnippetsView = atom.workspace.getModalPanels()[0].getItem()
-
- it "renders a select list of all available snippets", ->
- expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe 'test'
- expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe 'test'
- expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe '${1:Test pass you will}, young '
-
- availableSnippetsView.selectListView.selectNext()
-
- expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe 'chal'
- expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe 'challenge'
- expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe '$1: ${2:To pass this challenge}'
-
- it "writes the selected snippet to the editor as snippet", ->
- availableSnippetsView.selectListView.confirmSelection()
-
- expect(editor.getCursorScreenPosition()).toEqual [0, 18]
- expect(editor.getSelectedText()).toBe 'Test pass you will'
- expect(editor.lineTextForBufferRow(0)).toBe 'Test pass you will, young var quicksort = function () {'
-
- it "closes the dialog when triggered again", ->
- atom.commands.dispatch availableSnippetsView.selectListView.refs.queryEditor.element, 'snippets:available'
- expect(atom.workspace.getModalPanels().length).toBe 0
diff --git a/spec/snippets-spec.js b/spec/snippets-spec.js
new file mode 100644
index 00000000..345e1b9d
--- /dev/null
+++ b/spec/snippets-spec.js
@@ -0,0 +1,1148 @@
+const path = require('path');
+const temp = require('temp').track();
+const Snippets = require('../lib/snippets');
+const {TextEditor} = require('atom');
+
+describe("Snippets extension", () => {
+ let editorElement, editor;
+
+ const simulateTabKeyEvent = (param = {}) => {
+ const {shift} = param;
+ const event = atom.keymaps.constructor.buildKeydownEvent('tab', {shift, target: editorElement});
+ atom.keymaps.handleKeyboardEvent(event);
+ };
+
+ beforeEach(() => {
+ spyOn(Snippets, 'loadAll');
+ spyOn(Snippets, 'getUserSnippetsPath').andReturn('');
+
+ waitsForPromise(() => atom.workspace.open('sample.js'));
+
+ waitsForPromise(() => atom.packages.activatePackage('language-javascript'));
+
+ waitsForPromise(() => atom.packages.activatePackage('snippets'));
+
+ runs(() => {
+ editor = atom.workspace.getActiveTextEditor();
+ editorElement = atom.views.getView(editor);
+ });
+ });
+
+ afterEach(() => waitsForPromise(() => atom.packages.deactivatePackage('snippets')));
+
+ describe("provideSnippets interface", () => {
+ let snippetsInterface = null;
+
+ beforeEach(() => snippetsInterface = Snippets.provideSnippets());
+
+ describe("bundledSnippetsLoaded", () => {
+ it("indicates the loaded state of the bundled snippets", () => {
+ expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false);
+ Snippets.doneLoading();
+ expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true);
+ });
+
+ it("resets the loaded state after snippets is deactivated", () => {
+ expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false);
+ Snippets.doneLoading();
+ expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true);
+
+ waitsForPromise(() => atom.packages.deactivatePackage('snippets'));
+ waitsForPromise(() => atom.packages.activatePackage('snippets'));
+
+ runs(() => {
+ expect(snippetsInterface.bundledSnippetsLoaded()).toBe(false);
+ Snippets.doneLoading();
+ expect(snippetsInterface.bundledSnippetsLoaded()).toBe(true);
+ });
+ });
+ });
+
+ describe("insertSnippet", () => {
+ it("can insert a snippet", () => {
+ editor.setSelectedBufferRange([[0, 4], [0, 13]]);
+ snippetsInterface.insertSnippet("hello ${1:world}", editor);
+ expect(editor.lineTextForBufferRow(0)).toBe("var hello world = function () {");
+ });
+ });
+ });
+
+ it("returns false for snippetToExpandUnderCursor if getSnippets returns {}", () => {
+ const snippets = atom.packages.getActivePackage('snippets').mainModule;
+ expect(snippets.snippetToExpandUnderCursor(editor)).toEqual(false);
+ });
+
+ it("ignores invalid snippets in the config", () => {
+ const snippets = atom.packages.getActivePackage('snippets').mainModule;
+
+ let invalidSnippets = null;
+ spyOn(snippets.scopedPropertyStore, 'getPropertyValue').andCallFake(() => invalidSnippets);
+ expect(snippets.getSnippets(editor)).toEqual({});
+
+ invalidSnippets = 'test';
+ expect(snippets.getSnippets(editor)).toEqual({});
+
+ invalidSnippets = [];
+ expect(snippets.getSnippets(editor)).toEqual({});
+
+ invalidSnippets = 3;
+ expect(snippets.getSnippets(editor)).toEqual({});
+
+ invalidSnippets = {a: null};
+ expect(snippets.getSnippets(editor)).toEqual({});
+ });
+
+ describe("when null snippets are present", () => {
+ beforeEach(() => {
+ Snippets.add(__filename, {
+ ".source.js": {
+ "some snippet": {
+ prefix: "t1",
+ body: "this is a test"
+ }
+ },
+ ".source.js .nope": {
+ "some snippet": {
+ prefix: "t1",
+ body: null
+ }
+ }
+ });
+ });
+
+ it("overrides the less-specific defined snippet", () => {
+ const snippets = Snippets.provideSnippets();
+ expect(snippets.snippetsForScopes(['.source.js'])['t1']).toBeTruthy();
+ expect(snippets.snippetsForScopes(['.source.js .nope.not-today'])['t1']).toBeFalsy();
+ });
+ });
+
+ describe("when 'tab' is triggered on the editor", () => {
+ beforeEach(() => {
+ Snippets.add(__filename, {
+ ".source.js": {
+ "without tab stops": {
+ prefix: "t1",
+ body: "this is a test"
+ },
+ "with only an end tab stop": {
+ prefix: "t1a",
+ body: "something $0 strange"
+ },
+ "overlapping prefix": {
+ prefix: "tt1",
+ body: "this is another test"
+ },
+ "special chars": {
+ prefix: "@unique",
+ body: "@unique see"
+ },
+ "tab stops": {
+ prefix: "t2",
+ body: `\
+go here next:($2) and finally go here:($0)
+go here first:($1)
+\
+`
+ },
+ "indented second line": {
+ prefix: "t3",
+ body: `\
+line 1
+\tline 2$1
+$2\
+`
+ },
+ "multiline with indented placeholder tabstop": {
+ prefix: "t4",
+ body: `\
+line \${1:1}
+ \${2:body...}\
+`
+ },
+ "multiline starting with tabstop": {
+ prefix: "t4b",
+ body: `\
+$1 = line 1 {
+ line 2
+}\
+`
+ },
+ "nested tab stops": {
+ prefix: "t5",
+ body: '${1:"${2:key}"}: ${3:value}'
+ },
+ "caused problems with undo": {
+ prefix: "t6",
+ body: `\
+first line$1
+ \${2:placeholder ending second line}\
+`
+ },
+ "contains empty lines": {
+ prefix: "t7",
+ body: `\
+first line $1
+
+
+fourth line after blanks $2\
+`
+ },
+ "with/without placeholder": {
+ prefix: "t8",
+ body: `\
+with placeholder \${1:test}
+without placeholder \${2}\
+`
+ },
+ "multi-caret": {
+ prefix: "t9",
+ body: `\
+with placeholder \${1:test}
+without placeholder $1\
+`
+ },
+ "multi-caret-multi-tabstop": {
+ prefix: "t9b",
+ body: `\
+with placeholder \${1:test}
+without placeholder $1
+second tabstop $2
+third tabstop $3\
+`
+ },
+ "large indices": {
+ prefix: "t10",
+ body: `\
+hello\${10} \${11:large} indices\${1}\
+`
+ },
+ "no body": {
+ prefix: "bad1"
+ },
+ "number body": {
+ prefix: "bad2",
+ body: 100
+ },
+ "many tabstops": {
+ prefix: "t11",
+ body: `\
+$0one\${1} \${2:two} three\${3}\
+`
+ },
+ "simple transform": {
+ prefix: "t12",
+ body: `\
+[\${1:b}][/\${1/[ ]+.*$//}]\
+`
+ },
+ "transform with non-transforming mirrors": {
+ prefix: "t13",
+ body: `\
+\${1:placeholder}\n\${1/(.)/\\u$1/}\n$1\
+`
+ },
+ "multiple tab stops, some with transforms and some without": {
+ prefix: "t14",
+ body: `\
+\${1:placeholder} \${1/(.)/\\u$1/} $1 \${2:ANOTHER} \${2/^(.*)$/\\L$1/} $2\
+`
+ },
+ "has a transformed tab stop without a corresponding ordinary tab stop": {
+ prefix: 't15',
+ body: `\
+\${1/(.)/\\u$1/} & $2\
+`
+ },
+ "has a transformed tab stop that occurs before the corresponding ordinary tab stop": {
+ prefix: 't16',
+ body: `\
+& \${1/(.)/\\u$1/} & \${1:q}\
+`
+ },
+ "has a placeholder that mirrors another tab stop's content": {
+ prefix: 't17',
+ body: "$4console.${3:log}('${2:uh $1}', $1);$0"
+ },
+ "has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step": {
+ prefix: 't18',
+ body: '// $1\n// ${1/./=/}'
+ }
+ }
+ });
+ });
+
+ it("parses snippets once, reusing cached ones on subsequent queries", () => {
+ spyOn(Snippets, "getBodyParser").andCallThrough();
+
+ editor.insertText("t1");
+ simulateTabKeyEvent();
+
+ expect(Snippets.getBodyParser).toHaveBeenCalled();
+ expect(editor.lineTextForBufferRow(0)).toBe("this is a testvar quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 14]);
+
+ Snippets.getBodyParser.reset();
+
+ editor.setText("");
+ editor.insertText("t1");
+ simulateTabKeyEvent();
+
+ expect(Snippets.getBodyParser).not.toHaveBeenCalled();
+ expect(editor.lineTextForBufferRow(0)).toBe("this is a test");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 14]);
+
+ Snippets.getBodyParser.reset();
+
+ Snippets.add(__filename, {
+ ".source.js": {
+ "invalidate previous snippet": {
+ prefix: "t1",
+ body: "new snippet"
+ }
+ }
+ });
+
+ editor.setText("");
+ editor.insertText("t1");
+ simulateTabKeyEvent();
+
+ expect(Snippets.getBodyParser).toHaveBeenCalled();
+ expect(editor.lineTextForBufferRow(0)).toBe("new snippet");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 11]);
+ });
+
+ describe("when the snippet body is invalid or missing", () => {
+ it("does not register the snippet", () => {
+ editor.setText('');
+ editor.insertText('bad1');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ expect(editor.getText()).toBe('bad1');
+
+ editor.setText('');
+ editor.setText('bad2');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ expect(editor.getText()).toBe('bad2');
+ })
+ });
+
+ describe("when the letters preceding the cursor trigger a snippet", () => {
+ describe("when the snippet contains no tab stops", () => {
+ it("replaces the prefix with the snippet text and places the cursor at its end", () => {
+ editor.insertText("t1");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 2]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("this is a testvar quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 14]);
+ });
+
+ it("inserts a real tab the next time a tab is pressed after the snippet is expanded", () => {
+ editor.insertText("t1");
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("this is a testvar quicksort = function () {");
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("this is a test var quicksort = function () {");
+ });
+ });
+
+ describe("when the snippet contains tab stops", () => {
+ it("places the cursor at the first tab-stop, and moves the cursor in response to 'next-tab-stop' events", () => {
+ const markerCountBefore = editor.getMarkerCount();
+ editor.setCursorScreenPosition([2, 0]);
+ editor.insertText('t2');
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(2)).toBe("go here next:() and finally go here:()");
+ expect(editor.lineTextForBufferRow(3)).toBe("go here first:()");
+ expect(editor.lineTextForBufferRow(4)).toBe(" if (items.length <= 1) return items;");
+ expect(editor.getSelectedBufferRange()).toEqual([[3, 15], [3, 15]]);
+
+ simulateTabKeyEvent();
+ expect(editor.getSelectedBufferRange()).toEqual([[2, 14], [2, 14]]);
+ editor.insertText('abc');
+
+ simulateTabKeyEvent();
+ expect(editor.getSelectedBufferRange()).toEqual([[2, 40], [2, 40]]);
+
+ // tab backwards
+ simulateTabKeyEvent({shift: true});
+ expect(editor.getSelectedBufferRange()).toEqual([[2, 14], [2, 17]]); // should highlight text typed at tab stop
+
+ simulateTabKeyEvent({shift: true});
+ expect(editor.getSelectedBufferRange()).toEqual([[3, 15], [3, 15]]);
+
+ // shift-tab on first tab-stop does nothing
+ simulateTabKeyEvent({shift: true});
+ expect(editor.getCursorScreenPosition()).toEqual([3, 15]);
+
+ // tab through all tab stops, then tab on last stop to terminate snippet
+ simulateTabKeyEvent();
+ simulateTabKeyEvent();
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(2)).toBe("go here next:(abc) and finally go here:( )");
+ expect(editor.getMarkerCount()).toBe(markerCountBefore);
+ });
+
+ describe("when tab stops are nested", () => {
+ it("destroys the inner tab stop if the outer tab stop is modified", () => {
+ editor.setText('');
+ editor.insertText('t5');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ expect(editor.lineTextForBufferRow(0)).toBe('"key": value');
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 5]]);
+ editor.insertText("foo");
+ simulateTabKeyEvent();
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 10]]);
+ });
+ });
+
+ describe("when the only tab stop is an end stop", () => {
+ it("terminates the snippet immediately after moving the cursor to the end stop", () => {
+ editor.setText('');
+ editor.insertText('t1a');
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(0)).toBe("something strange");
+ expect(editor.getCursorBufferPosition()).toEqual([0, 10]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("something strange");
+ expect(editor.getCursorBufferPosition()).toEqual([0, 12]);
+ });
+ });
+
+ describe("when tab stops are separated by blank lines", () => {
+ it("correctly places the tab stops (regression)", () => {
+ editor.setText('');
+ editor.insertText('t7');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ atom.commands.dispatch(editorElement, 'snippets:next-tab-stop');
+ expect(editor.getCursorBufferPosition()).toEqual([3, 25]);
+ });
+ });
+
+ describe("when the cursor is moved beyond the bounds of the current tab stop", () => {
+ it("terminates the snippet", () => {
+ editor.setCursorScreenPosition([2, 0]);
+ editor.insertText('t2');
+ simulateTabKeyEvent();
+
+ editor.moveUp();
+ editor.moveLeft();
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(2)).toBe("go here next:( ) and finally go here:()");
+ expect(editor.getCursorBufferPosition()).toEqual([2, 16]);
+
+ // test we can terminate with shift-tab
+ editor.setCursorScreenPosition([4, 0]);
+ editor.insertText('t2');
+ simulateTabKeyEvent();
+ simulateTabKeyEvent();
+
+ editor.moveRight();
+ simulateTabKeyEvent({shift: true});
+ expect(editor.getCursorBufferPosition()).toEqual([4, 15]);
+ });
+ });
+
+ describe("when the cursor is moved within the bounds of the current tab stop", () => {
+ it("should not terminate the snippet", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t8');
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test");
+ editor.moveRight();
+ editor.moveLeft();
+ editor.insertText("foo");
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder tesfoot");
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {");
+ editor.insertText("test");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder testvar quicksort = function () {");
+ editor.moveLeft();
+ editor.insertText("foo");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder tesfootvar quicksort = function () {");
+ });
+ });
+
+ describe("when the backspace is press within the bounds of the current tab stop", () => {
+ it("should not terminate the snippet", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t8');
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test");
+ editor.moveRight();
+ editor.backspace();
+ editor.insertText("foo");
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder tesfoo");
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {");
+ editor.insertText("test");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder testvar quicksort = function () {");
+ editor.backspace();
+ editor.insertText("foo");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder tesfoovar quicksort = function () {");
+ });
+ });
+ });
+
+ describe("when the snippet contains hard tabs", () => {
+ describe("when the edit session is in soft-tabs mode", () => {
+ it("translates hard tabs in the snippet to the appropriate number of spaces", () => {
+ expect(editor.getSoftTabs()).toBeTruthy();
+ editor.insertText("t3");
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(1)).toBe(" line 2");
+ expect(editor.getCursorBufferPosition()).toEqual([1, 8]);
+ });
+ });
+
+ describe("when the edit session is in hard-tabs mode", () => {
+ it("inserts hard tabs in the snippet directly", () => {
+ editor.setSoftTabs(false);
+ editor.insertText("t3");
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(1)).toBe("\tline 2");
+ expect(editor.getCursorBufferPosition()).toEqual([1, 7]);
+ });
+ });
+ });
+
+ describe("when the snippet prefix is indented", () => {
+ describe("when the snippet spans a single line", () => {
+ it("does not indent the next line", () => {
+ editor.setCursorScreenPosition([2, Infinity]);
+ editor.insertText(' t1');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ expect(editor.lineTextForBufferRow(3)).toBe(" var pivot = items.shift(), current, left = [], right = [];");
+ });
+ });
+
+ describe("when the snippet spans multiple lines", () => {
+ it("indents the subsequent lines of the snippet to be even with the start of the first line", () => {
+ expect(editor.getSoftTabs()).toBeTruthy();
+ editor.setCursorScreenPosition([2, Infinity]);
+ editor.insertText(' t3');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ expect(editor.lineTextForBufferRow(2)).toBe(" if (items.length <= 1) return items; line 1");
+ expect(editor.lineTextForBufferRow(3)).toBe(" line 2");
+ expect(editor.getCursorBufferPosition()).toEqual([3, 12]);
+ });
+ });
+ });
+
+ describe("when the snippet spans multiple lines", () => {
+ beforeEach(() => {
+ editor.update({autoIndent: true});
+ });
+
+ it("places tab stops correctly", () => {
+ expect(editor.getSoftTabs()).toBeTruthy();
+ editor.setCursorScreenPosition([2, Infinity]);
+ editor.insertText(' t3');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+ expect(editor.getCursorBufferPosition()).toEqual([3, 12]);
+ atom.commands.dispatch(editorElement, 'snippets:next-tab-stop');
+ expect(editor.getCursorBufferPosition()).toEqual([4, 4]);
+ });
+
+ it("indents the subsequent lines of the snippet based on the indent level before the snippet is inserted", () => {
+ editor.setCursorScreenPosition([2, Infinity]);
+ editor.insertNewline();
+ editor.insertText('t4b');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+
+ expect(editor.lineTextForBufferRow(3)).toBe(" = line 1 {"); // 4 + 1 spaces (because the tab stop is invisible)
+ expect(editor.lineTextForBufferRow(4)).toBe(" line 2");
+ expect(editor.lineTextForBufferRow(5)).toBe(" }");
+ expect(editor.getCursorBufferPosition()).toEqual([3, 4]);
+ });
+
+ it("does not change the relative positioning of the tab stops when inserted multiple times", () => {
+ editor.setCursorScreenPosition([2, Infinity]);
+ editor.insertNewline();
+ editor.insertText('t4');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+
+ expect(editor.getSelectedBufferRange()).toEqual([[3, 9], [3, 10]]);
+ atom.commands.dispatch(editorElement, 'snippets:next-tab-stop');
+ expect(editor.getSelectedBufferRange()).toEqual([[4, 6], [4, 13]]);
+
+ editor.insertText('t4');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+
+ expect(editor.getSelectedBufferRange()).toEqual([[4, 11], [4, 12]]);
+ atom.commands.dispatch(editorElement, 'snippets:next-tab-stop');
+ expect(editor.getSelectedBufferRange()).toEqual([[5, 8], [5, 15]]);
+
+ editor.setText(''); // Clear editor
+ editor.insertText('t4');
+ atom.commands.dispatch(editorElement, 'snippets:expand');
+
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 5], [0, 6]]);
+ atom.commands.dispatch(editorElement, 'snippets:next-tab-stop');
+ expect(editor.getSelectedBufferRange()).toEqual([[1, 2], [1, 9]]);
+ });
+ });
+
+ describe("when multiple snippets match the prefix", () => {
+ it("expands the snippet that is the longest match for the prefix", () => {
+ editor.insertText('t113');
+ expect(editor.getCursorScreenPosition()).toEqual([0, 4]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("t113 var quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 6]);
+
+ editor.undo();
+ editor.undo();
+
+ editor.insertText("tt1");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 3]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("this is another testvar quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 20]);
+
+ editor.undo();
+ editor.undo();
+
+ editor.insertText("@t1");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 3]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("@this is a testvar quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 15]);
+ });
+ });
+ });
+
+ describe("when the word preceding the cursor ends with a snippet prefix", () => {
+ it("inserts a tab as normal", () => {
+ editor.insertText("t1t1t1");
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("t1t1t1 var quicksort = function () {");
+ });
+ });
+
+ describe("when the letters preceding the cursor don't match a snippet", () => {
+ it("inserts a tab as normal", () => {
+ editor.insertText("xxte");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 4]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("xxte var quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 6]);
+ });
+ });
+
+ describe("when text is selected", () => {
+ it("inserts a tab as normal", () => {
+ editor.insertText("t1");
+ editor.setSelectedBufferRange([[0, 0], [0, 2]]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe(" t1var quicksort = function () {");
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 0], [0, 4]]);
+ });
+ });
+
+ describe("when a previous snippet expansion has just been undone", () => {
+ it("expands the snippet based on the current prefix rather than jumping to the old snippet's tab stop", () => {
+ editor.insertText('t6\n');
+ editor.setCursorBufferPosition([0, 2]);
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("first line");
+ editor.undo();
+ expect(editor.lineTextForBufferRow(0)).toBe("t6");
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("first line");
+ });
+ });
+
+ describe("when the prefix contains non-word characters", () => {
+ it("selects the non-word characters as part of the prefix", () => {
+ editor.insertText("@unique");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 7]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("@unique seevar quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 11]);
+
+ editor.setCursorBufferPosition([10, 0]);
+ editor.insertText("'@unique");
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(10)).toBe("'@unique see");
+ expect(editor.getCursorScreenPosition()).toEqual([10, 12]);
+ });
+
+ it("does not select the whitespace before the prefix", () => {
+ editor.insertText("a; @unique");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 10]);
+
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("a; @unique seevar quicksort = function () {");
+ expect(editor.getCursorScreenPosition()).toEqual([0, 14]);
+ });
+ });
+
+ describe("when snippet contains tabstops with or without placeholder", () => {
+ it("should create two markers", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t8');
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {");
+
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 17], [0, 21]]);
+
+ simulateTabKeyEvent();
+ expect(editor.getSelectedBufferRange()).toEqual([[1, 20], [1, 20]]);
+ });
+ });
+
+ describe("when snippet contains multi-caret tabstops with or without placeholder", () => {
+ it("should create two markers", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t9');
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {");
+ editor.insertText('hello');
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder hello");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder hellovar quicksort = function () {");
+ });
+
+ it("terminates the snippet when cursors are destroyed", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t9b');
+ simulateTabKeyEvent();
+ editor.getCursors()[0].destroy();
+ editor.getCursorBufferPosition();
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(1)).toEqual("without placeholder ");
+ });
+
+ it("terminates the snippet expansion if a new cursor moves outside the bounds of the tab stops", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t9b');
+ simulateTabKeyEvent();
+ editor.insertText('test');
+
+ editor.getCursors()[0].destroy();
+ editor.moveDown(); // this should destroy the previous expansion
+ editor.moveToBeginningOfLine();
+
+ // this should insert whitespace instead of going through tabstops of the previous destroyed snippet
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(2).indexOf(" second")).toBe(0);
+ });
+
+ it("moves to the second tabstop after a multi-caret tabstop", () => {
+ editor.setCursorScreenPosition([0, 0]);
+ editor.insertText('t9b');
+ simulateTabKeyEvent();
+ editor.insertText('line 1');
+
+ simulateTabKeyEvent();
+ editor.insertText('line 2');
+
+ simulateTabKeyEvent();
+ editor.insertText('line 3');
+
+ expect(editor.lineTextForBufferRow(2).indexOf("line 2 ")).toBe(-1);
+ });
+
+ it("mirrors input properly when a tabstop's placeholder refers to another tabstop", () => {
+ editor.setText('t17');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ editor.insertText("foo");
+ expect(editor.getText()).toBe("console.log('uh foo', foo);");
+ simulateTabKeyEvent();
+ editor.insertText("bar");
+ expect(editor.getText()).toBe("console.log('bar', foo);");
+ });
+ });
+
+ describe("when the snippet contains tab stops with transformations", () => {
+ it("transforms the text typed into the first tab stop before setting it in the transformed tab stop", () => {
+ editor.setText('t12');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ expect(editor.getText()).toBe("[b][/b]");
+ editor.insertText('img src');
+ expect(editor.getText()).toBe("[img src][/img]");
+ });
+
+ it("bundles the transform mutations along with the original manual mutation for the purposes of undo and redo", () => {
+ editor.setText('t12');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ editor.insertText('i');
+ expect(editor.getText()).toBe("[i][/i]");
+
+ editor.insertText('mg src');
+ expect(editor.getText()).toBe("[img src][/img]");
+
+ editor.undo();
+ expect(editor.getText()).toBe("[i][/i]");
+
+ editor.redo();
+ expect(editor.getText()).toBe("[img src][/img]");
+ });
+
+ it("can pick the right insertion to use as the primary even if a transformed insertion occurs first in the snippet", () => {
+ editor.setText('t16');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("& Q & q");
+ expect(editor.getCursorBufferPosition()).toEqual([0, 7]);
+
+ editor.insertText('rst');
+ expect(editor.lineTextForBufferRow(0)).toBe("& RST & rst");
+ });
+
+ it("silently ignores a tab stop without a non-transformed insertion to use as the primary", () => {
+ editor.setText('t15');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ editor.insertText('a');
+ expect(editor.lineTextForBufferRow(0)).toBe(" & a");
+ expect(editor.getCursorBufferPosition()).toEqual([0, 4]);
+ });
+ });
+
+ describe("when the snippet contains mirrored tab stops and tab stops with transformations", () => {
+ it("adds cursors for the mirrors but not the transformations", () => {
+ editor.setText('t13');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ expect(editor.getCursors().length).toBe(2);
+ expect(editor.getText()).toBe("placeholder\nPLACEHOLDER\n");
+ editor.insertText('foo');
+ expect(editor.getText()).toBe("foo\nFOO\nfoo");
+ });
+ });
+
+ describe("when the snippet contains multiple tab stops, some with transformations and some without", () => {
+ it("does not get confused", () => {
+ editor.setText('t14');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ expect(editor.getCursors().length).toBe(2);
+ expect(editor.getText()).toBe("placeholder PLACEHOLDER ANOTHER another ");
+ simulateTabKeyEvent();
+ expect(editor.getCursors().length).toBe(2);
+ editor.insertText('FOO');
+ expect(editor.getText()).toBe("placeholder PLACEHOLDER FOO foo FOO");
+ });
+ });
+
+ describe("when the snippet has a transformed tab stop such that it is possible to move the cursor between the ordinary tab stop and its transformed version without an intermediate step", () => {
+ it("terminates the snippet upon such a cursor move", () => {
+ editor.setText('t18');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ expect(editor.getText()).toBe("// \n// ");
+ expect(editor.getCursorBufferPosition()).toEqual([0, 3]);
+ editor.insertText('wat');
+ expect(editor.getText()).toBe("// wat\n// ===");
+ // Move the cursor down one line, then up one line. This puts the cursor
+ // back in its previous position, but the snippet should no longer be
+ // active, so when we type more text, it should not be mirrored.
+ editor.setCursorScreenPosition([1, 6]);
+ editor.setCursorScreenPosition([0, 6]);
+ editor.insertText('wat');
+ expect(editor.getText()).toBe("// watwat\n// ===");
+ });
+ });
+
+
+ describe("when the snippet contains tab stops with an index >= 10", () => {
+ it("parses and orders the indices correctly", () => {
+ editor.setText('t10');
+ editor.setCursorScreenPosition([0, 3]);
+ simulateTabKeyEvent();
+ expect(editor.getText()).toBe("hello large indices");
+ expect(editor.getCursorBufferPosition()).toEqual([0, 19]);
+ simulateTabKeyEvent();
+ expect(editor.getCursorBufferPosition()).toEqual([0, 5]);
+ simulateTabKeyEvent();
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 6], [0, 11]]);
+ });
+ });
+
+ describe("when there are multiple cursors", () => {
+ describe("when the cursors share a common snippet prefix", () => {
+ it("expands the snippet for all cursors and allows simultaneous editing", () => {
+ editor.insertText('t9');
+ editor.setCursorBufferPosition([12, 2]);
+ editor.insertText(' t9');
+ editor.addCursorAtBufferPosition([0, 2]);
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder test");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder var quicksort = function () {");
+ expect(editor.lineTextForBufferRow(13)).toBe("}; with placeholder test");
+ expect(editor.lineTextForBufferRow(14)).toBe("without placeholder ");
+
+ editor.insertText('hello');
+ expect(editor.lineTextForBufferRow(0)).toBe("with placeholder hello");
+ expect(editor.lineTextForBufferRow(1)).toBe("without placeholder hellovar quicksort = function () {");
+ expect(editor.lineTextForBufferRow(13)).toBe("}; with placeholder hello");
+ expect(editor.lineTextForBufferRow(14)).toBe("without placeholder hello");
+ });
+
+ it("applies transformations identically to single-expansion mode", () => {
+ editor.setText('t14\nt14');
+ editor.setCursorBufferPosition([1, 3]);
+ editor.addCursorAtBufferPosition([0, 3]);
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(0)).toBe("placeholder PLACEHOLDER ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("placeholder PLACEHOLDER ANOTHER another ");
+
+ editor.insertText("testing");
+
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another ");
+
+ simulateTabKeyEvent();
+ editor.insertText("AGAIN");
+
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing AGAIN again AGAIN");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing AGAIN again AGAIN");
+ });
+
+ it("bundles transform-induced mutations into a single history entry along with their triggering edit, even across multiple snippets", () => {
+ editor.setText('t14\nt14');
+ editor.setCursorBufferPosition([1, 3]);
+ editor.addCursorAtBufferPosition([0, 3]);
+ simulateTabKeyEvent();
+
+ expect(editor.lineTextForBufferRow(0)).toBe("placeholder PLACEHOLDER ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("placeholder PLACEHOLDER ANOTHER another ");
+
+ editor.insertText("testing");
+
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another ");
+
+ simulateTabKeyEvent();
+ editor.insertText("AGAIN");
+
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing AGAIN again AGAIN");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing AGAIN again AGAIN");
+
+ editor.undo();
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another ");
+
+ editor.undo();
+ expect(editor.lineTextForBufferRow(0)).toBe("placeholder PLACEHOLDER ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("placeholder PLACEHOLDER ANOTHER another ");
+
+ editor.redo();
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing ANOTHER another ");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing ANOTHER another ");
+
+ editor.redo();
+ expect(editor.lineTextForBufferRow(0)).toBe("testing TESTING testing AGAIN again AGAIN");
+ expect(editor.lineTextForBufferRow(1)).toBe("testing TESTING testing AGAIN again AGAIN");
+ });
+
+ describe("when there are many tabstops", () => {
+ it("moves the cursors between the tab stops for their corresponding snippet when tab and shift-tab are pressed", () => {
+ editor.addCursorAtBufferPosition([7, 5]);
+ editor.addCursorAtBufferPosition([12, 2]);
+ editor.insertText('t11');
+ simulateTabKeyEvent();
+
+ const cursors = editor.getCursors();
+ expect(cursors.length).toEqual(3);
+
+ expect(cursors[0].getBufferPosition()).toEqual([0, 3]);
+ expect(cursors[1].getBufferPosition()).toEqual([7, 8]);
+ expect(cursors[2].getBufferPosition()).toEqual([12, 5]);
+ expect(cursors[0].selection.isEmpty()).toBe(true);
+ expect(cursors[1].selection.isEmpty()).toBe(true);
+ expect(cursors[2].selection.isEmpty()).toBe(true);
+
+ simulateTabKeyEvent();
+ expect(cursors[0].getBufferPosition()).toEqual([0, 7]);
+ expect(cursors[1].getBufferPosition()).toEqual([7, 12]);
+ expect(cursors[2].getBufferPosition()).toEqual([12, 9]);
+ expect(cursors[0].selection.isEmpty()).toBe(false);
+ expect(cursors[1].selection.isEmpty()).toBe(false);
+ expect(cursors[2].selection.isEmpty()).toBe(false);
+ expect(cursors[0].selection.getText()).toEqual('two');
+ expect(cursors[1].selection.getText()).toEqual('two');
+ expect(cursors[2].selection.getText()).toEqual('two');
+
+ simulateTabKeyEvent();
+ expect(cursors[0].getBufferPosition()).toEqual([0, 13]);
+ expect(cursors[1].getBufferPosition()).toEqual([7, 18]);
+ expect(cursors[2].getBufferPosition()).toEqual([12, 15]);
+ expect(cursors[0].selection.isEmpty()).toBe(true);
+ expect(cursors[1].selection.isEmpty()).toBe(true);
+ expect(cursors[2].selection.isEmpty()).toBe(true);
+
+ simulateTabKeyEvent();
+ expect(cursors[0].getBufferPosition()).toEqual([0, 0]);
+ expect(cursors[1].getBufferPosition()).toEqual([7, 5]);
+ expect(cursors[2].getBufferPosition()).toEqual([12, 2]);
+ expect(cursors[0].selection.isEmpty()).toBe(true);
+ expect(cursors[1].selection.isEmpty()).toBe(true);
+ expect(cursors[2].selection.isEmpty()).toBe(true);
+ });
+ });
+ });
+
+ describe("when the cursors do not share common snippet prefixes", () => {
+ it("inserts tabs as normal", () => {
+ editor.insertText('t9');
+ editor.setCursorBufferPosition([12, 2]);
+ editor.insertText(' t8');
+ editor.addCursorAtBufferPosition([0, 2]);
+ simulateTabKeyEvent();
+ expect(editor.lineTextForBufferRow(0)).toBe("t9 var quicksort = function () {");
+ expect(editor.lineTextForBufferRow(12)).toBe("}; t8 ");
+ });
+ });
+
+ describe("when a snippet is triggered within an existing snippet expansion", () => {
+ it("ignores the snippet expansion and goes to the next tab stop", () => {
+ editor.addCursorAtBufferPosition([7, 5]);
+ editor.addCursorAtBufferPosition([12, 2]);
+ editor.insertText('t11');
+ simulateTabKeyEvent();
+ simulateTabKeyEvent();
+
+ editor.insertText('t1');
+ simulateTabKeyEvent();
+
+ const cursors = editor.getCursors();
+ expect(cursors.length).toEqual(3);
+
+ expect(cursors[0].getBufferPosition()).toEqual([0, 12]);
+ expect(cursors[1].getBufferPosition()).toEqual([7, 17]);
+ expect(cursors[2].getBufferPosition()).toEqual([12, 14]);
+ expect(cursors[0].selection.isEmpty()).toBe(true);
+ expect(cursors[1].selection.isEmpty()).toBe(true);
+ expect(cursors[2].selection.isEmpty()).toBe(true);
+ expect(editor.lineTextForBufferRow(0)).toBe("one t1 threevar quicksort = function () {");
+ expect(editor.lineTextForBufferRow(7)).toBe(" }one t1 three");
+ expect(editor.lineTextForBufferRow(12)).toBe("};one t1 three");
+ });
+ });
+ });
+
+ describe("when the editor is not a pane item (regression)", () => {
+ it("handles tab stops correctly", () => {
+ editor = new TextEditor();
+ atom.grammars.assignLanguageMode(editor, 'source.js');
+ editorElement = editor.getElement();
+
+ editor.insertText('t2');
+ simulateTabKeyEvent();
+ editor.insertText('ABC');
+ expect(editor.getText()).toContain('go here first:(ABC)');
+
+ editor.undo();
+ editor.undo();
+ expect(editor.getText()).toBe('t2');
+ simulateTabKeyEvent();
+ editor.insertText('ABC');
+ expect(editor.getText()).toContain('go here first:(ABC)');
+ });
+ });
+ });
+
+ describe("when atom://.atom/snippets is opened", () => {
+ it("opens ~/.atom/snippets.cson", () => {
+ jasmine.unspy(Snippets, 'getUserSnippetsPath');
+ atom.workspace.destroyActivePaneItem();
+ const configDirPath = temp.mkdirSync('atom-config-dir-');
+ spyOn(atom, 'getConfigDirPath').andReturn(configDirPath);
+ atom.workspace.open('atom://.atom/snippets');
+
+ waitsFor(() => atom.workspace.getActiveTextEditor() != null);
+
+ runs(() => {
+ expect(atom.workspace.getActiveTextEditor().getURI()).toBe(path.join(configDirPath, 'snippets.cson'));
+ });
+ });
+ });
+
+ describe("snippet insertion API", () => {
+ it("will automatically parse snippet definition and replace selection", () => {
+ editor.setSelectedBufferRange([[0, 4], [0, 13]]);
+ Snippets.insert("hello ${1:world}", editor);
+
+ expect(editor.lineTextForBufferRow(0)).toBe("var hello world = function () {");
+ expect(editor.getSelectedBufferRange()).toEqual([[0, 10], [0, 15]]);
+ });
+ });
+
+ describe("when the 'snippets:available' command is triggered", () => {
+ let availableSnippetsView = null;
+
+ beforeEach(() => {
+ Snippets.add(__filename, {
+ ".source.js": {
+ "test": {
+ prefix: "test",
+ body: "${1:Test pass you will}, young "
+ },
+
+ "challenge": {
+ prefix: "chal",
+ body: "$1: ${2:To pass this challenge}"
+ }
+ }
+ });
+
+ delete Snippets.availableSnippetsView;
+
+ atom.commands.dispatch(editorElement, "snippets:available");
+
+ waitsFor(() => atom.workspace.getModalPanels().length === 1);
+
+ runs(() => {
+ availableSnippetsView = atom.workspace.getModalPanels()[0].getItem();
+ });
+ });
+
+ it("renders a select list of all available snippets", () => {
+ expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe('test');
+ expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe('test');
+ expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe('${1:Test pass you will}, young ');
+
+ availableSnippetsView.selectListView.selectNext();
+
+ expect(availableSnippetsView.selectListView.getSelectedItem().prefix).toBe('chal');
+ expect(availableSnippetsView.selectListView.getSelectedItem().name).toBe('challenge');
+ expect(availableSnippetsView.selectListView.getSelectedItem().bodyText).toBe('$1: ${2:To pass this challenge}');
+ });
+
+ it("writes the selected snippet to the editor as snippet", () => {
+ availableSnippetsView.selectListView.confirmSelection();
+
+ expect(editor.getCursorScreenPosition()).toEqual([0, 18]);
+ expect(editor.getSelectedText()).toBe('Test pass you will');
+ expect(editor.lineTextForBufferRow(0)).toBe('Test pass you will, young var quicksort = function () {');
+ });
+
+ it("closes the dialog when triggered again", () => {
+ atom.commands.dispatch(availableSnippetsView.selectListView.refs.queryEditor.element, 'snippets:available');
+ expect(atom.workspace.getModalPanels().length).toBe(0);
+ });
+ });
+});