1package sourcemap_test
2
3import (
4	"encoding/json"
5	"fmt"
6	"io/ioutil"
7	"net/http"
8	"strings"
9	"testing"
10
11	"github.com/go-sourcemap/sourcemap"
12)
13
14const jqSourceMapURL = "http://code.jquery.com/jquery-2.0.3.min.map"
15
16var jqSourceMapBytes []byte
17
18func init() {
19	resp, err := http.Get(jqSourceMapURL)
20	if err != nil {
21		panic(err)
22	}
23	defer resp.Body.Close()
24
25	jqSourceMapBytes, err = ioutil.ReadAll(resp.Body)
26	if err != nil {
27		panic(err)
28	}
29}
30
31type sourceMapTest struct {
32	genLine      int
33	genColumn    int
34	wantedSource string
35	wantedName   string
36	wantedLine   int
37	wantedColumn int
38}
39
40func (test *sourceMapTest) String() string {
41	return fmt.Sprintf("line=%d col=%d in file=%s", test.genLine, test.genColumn, test.wantedSource)
42}
43
44func (test *sourceMapTest) assert(t *testing.T, smap *sourcemap.Consumer) {
45	source, name, line, col, ok := smap.Source(test.genLine, test.genColumn)
46	if !ok {
47		if test.wantedSource == "" &&
48			test.wantedName == "" &&
49			test.wantedLine == 0 &&
50			test.wantedColumn == 0 {
51			return
52		}
53		t.Fatalf("Source not found for %s", test)
54	}
55	if source != test.wantedSource {
56		t.Fatalf("file: got %q, wanted %q (%s)", source, test.wantedSource, test)
57	}
58	if name != test.wantedName {
59		t.Fatalf("func: got %q, wanted %q (%s)", name, test.wantedName, test)
60	}
61	if line != test.wantedLine {
62		t.Fatalf("line: got %d, wanted %d (%s)", line, test.wantedLine, test)
63	}
64	if col != test.wantedColumn {
65		t.Fatalf("column: got %d, wanted %d (%s)", col, test.wantedColumn, test)
66	}
67}
68
69func TestSourceMap(t *testing.T) {
70	testSourceMap(t, sourceMapJSON)
71}
72
73func TestIndexedSourceMap(t *testing.T) {
74	testSourceMap(t, indexedSourceMapJSON)
75}
76
77func testSourceMap(t *testing.T, json string) {
78	smap, err := sourcemap.Parse("", []byte(json))
79	if err != nil {
80		t.Fatal(err)
81	}
82
83	tests := []sourceMapTest{
84		{1, 1, "/the/root/one.js", "", 1, 1},
85		{1, 5, "/the/root/one.js", "", 1, 5},
86		{1, 9, "/the/root/one.js", "", 1, 11},
87		{1, 18, "/the/root/one.js", "bar", 1, 21},
88		{1, 21, "/the/root/one.js", "", 2, 3},
89		{1, 28, "/the/root/one.js", "baz", 2, 10},
90		{1, 32, "/the/root/one.js", "bar", 2, 14},
91
92		{2, 1, "/the/root/two.js", "", 1, 1},
93		{2, 5, "/the/root/two.js", "", 1, 5},
94		{2, 9, "/the/root/two.js", "", 1, 11},
95		{2, 18, "/the/root/two.js", "n", 1, 21},
96		{2, 21, "/the/root/two.js", "", 2, 3},
97		{2, 28, "/the/root/two.js", "n", 2, 10},
98
99		// Fuzzy match.
100		{1, 20, "/the/root/one.js", "bar", 1, 21},
101		{1, 30, "/the/root/one.js", "baz", 2, 10},
102		{2, 12, "/the/root/two.js", "", 1, 11},
103	}
104	for i := range tests {
105		tests[i].assert(t, smap)
106	}
107
108	content := smap.SourceContent("/the/root/one.js")
109	if content != oneSourceContent {
110		t.Fatalf("%q != %q", content, oneSourceContent)
111	}
112
113	content = smap.SourceContent("/the/root/two.js")
114	if content != twoSourceContent {
115		t.Fatalf("%q != %q", content, twoSourceContent)
116	}
117
118	_, _, _, _, ok := smap.Source(3, 0)
119	if ok {
120		t.Fatal("source must not exist")
121	}
122}
123
124func TestSourceRootURL(t *testing.T) {
125	jsonStr := sourceMapJSON
126	jsonStr = strings.Replace(jsonStr, "/the/root", "http://the/root", 1)
127	jsonStr = strings.Replace(jsonStr, "one.js", "../one.js", 1)
128
129	smap, err := sourcemap.Parse("", []byte(jsonStr))
130	if err != nil {
131		t.Fatal(err)
132	}
133
134	tests := []*sourceMapTest{
135		{1, 1, "http://the/one.js", "", 1, 1},
136		{2, 1, "http://the/root/two.js", "", 1, 1},
137	}
138	for _, test := range tests {
139		test.assert(t, smap)
140	}
141}
142
143func TestEmptySourceRootURL(t *testing.T) {
144	jsonStr := sourceMapJSON
145	jsonStr = strings.Replace(jsonStr, "/the/root", "", 1)
146	jsonStr = strings.Replace(jsonStr, "one.js", "../one.js", 1)
147
148	smap, err := sourcemap.Parse("http://the/root/app.min.map", []byte(jsonStr))
149	if err != nil {
150		t.Fatal(err)
151	}
152
153	tests := []*sourceMapTest{
154		{1, 1, "http://the/one.js", "", 1, 1},
155		{2, 1, "http://the/root/two.js", "", 1, 1},
156	}
157	for _, test := range tests {
158		test.assert(t, smap)
159	}
160}
161
162func TestAbsSourceURL(t *testing.T) {
163	jsonStr := sourceMapJSON
164	jsonStr = strings.Replace(jsonStr, "/the/root", "", 1)
165	jsonStr = strings.Replace(jsonStr, "one.js", "http://the/root/one.js", 1)
166	jsonStr = strings.Replace(jsonStr, "two.js", "/another/root/two.js", 1)
167
168	testAbsSourceURL(t, "", jsonStr)
169	testAbsSourceURL(t, "http://path/to/map", jsonStr)
170}
171
172func testAbsSourceURL(t *testing.T, mapURL, jsonStr string) {
173	smap, err := sourcemap.Parse(mapURL, []byte(jsonStr))
174	if err != nil {
175		t.Fatal(err)
176	}
177
178	tests := []*sourceMapTest{
179		{1, 1, "http://the/root/one.js", "", 1, 1},
180		{2, 1, "/another/root/two.js", "", 1, 1},
181	}
182	for _, test := range tests {
183		test.assert(t, smap)
184	}
185}
186
187func TestJQuerySourceMap(t *testing.T) {
188	smap, err := sourcemap.Parse(jqSourceMapURL, jqSourceMapBytes)
189	if err != nil {
190		t.Fatal(err)
191	}
192
193	tests := []*sourceMapTest{
194		{1, 1, "", "", 0, 0},
195		{4, 0, "", "", 0, 0},
196		{4, 1, "http://code.jquery.com/jquery-2.0.3.js", "", 14, 0},
197		{4, 10, "http://code.jquery.com/jquery-2.0.3.js", "window", 14, 11},
198		{5, 6789, "http://code.jquery.com/jquery-2.0.3.js", "apply", 4360, 27},
199		{5, 10006, "http://code.jquery.com/jquery-2.0.3.js", "apply", 4676, 8},
200		{4, 553, "http://code.jquery.com/jquery-2.0.3.js", "ready", 93, 9},
201		{999999, 0, "", "", 0, 0},
202	}
203	for _, test := range tests {
204		test.assert(t, smap)
205	}
206}
207
208// https://github.com/mozilla/source-map/blob/master/test/util.js
209//
210// This is a test mapping which maps functions from two different files
211// (one.js and two.js) to a minified generated source.
212//
213// Here is one.js:
214//
215//     ONE.foo = function (bar) {
216//       return baz(bar);
217//     };
218//
219// Here is two.js:
220//
221//     TWO.inc = function (n) {
222//       return n + 1;
223//     };
224//
225// And here is the generated code (min.js):
226//
227//     ONE.foo=function(a){return baz(a);};
228//     TWO.inc=function(a){return a+1;};
229
230const genCode = `exports.testGeneratedCode = "ONE.foo=function(a){return baz(a);};
231TWO.inc=function(a){return a+1;};`
232
233var oneSourceContent = `ONE.foo = function (bar) {
234  return baz(bar);
235};`
236
237var twoSourceContent = `TWO.inc = function (n) {
238  return n + 1;
239};`
240
241var sourceMapJSON = `{
242  "version": 3,
243  "file": "min.js",
244  "sources": ["one.js", "two.js"],
245  "sourcesContent": ` + j([]string{oneSourceContent, twoSourceContent}) + `,
246  "sourceRoot": "/the/root",
247  "names": ["bar", "baz", "n"],
248  "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
249}`
250
251func j(v interface{}) string {
252	b, _ := json.Marshal(v)
253	return string(b)
254}
255
256var indexedSourceMapJSON = `{
257  "version": 3,
258  "file": "min.js",
259  "sections": [{
260    "offset": {"line": 0, "column": 0},
261    "map": {
262      "version": 3,
263      "file": "min.js",
264      "sources": ["one.js"],
265      "sourcesContent": ` + j([]string{oneSourceContent}) + `,
266      "sourceRoot": "/the/root",
267      "names": ["bar", "baz"],
268      "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID"
269    }
270  }, {
271    "offset": {"line": 1, "column": 0},
272    "map": {
273      "version": 3,
274      "file": "min.js",
275      "sources": ["two.js"],
276      "sourcesContent": ` + j([]string{twoSourceContent}) + `,
277      "sourceRoot": "/the/root",
278      "names": ["n"],
279      "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA"
280    }
281  }]
282}`
283