render_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // Copyright 2013 Martini Authors
  2. // Copyright 2014 Unknwon
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. package macaron
  16. import (
  17. "encoding/xml"
  18. "html/template"
  19. "net/http"
  20. "net/http/httptest"
  21. "testing"
  22. "time"
  23. . "github.com/smartystreets/goconvey/convey"
  24. )
  25. type Greeting struct {
  26. One string `json:"one"`
  27. Two string `json:"two"`
  28. }
  29. type GreetingXML struct {
  30. XMLName xml.Name `xml:"greeting"`
  31. One string `xml:"one,attr"`
  32. Two string `xml:"two,attr"`
  33. }
  34. func Test_Render_JSON(t *testing.T) {
  35. Convey("Render JSON", t, func() {
  36. m := Classic()
  37. m.Use(Renderer())
  38. m.Get("/foobar", func(r Render) {
  39. r.JSON(300, Greeting{"hello", "world"})
  40. })
  41. resp := httptest.NewRecorder()
  42. req, err := http.NewRequest("GET", "/foobar", nil)
  43. So(err, ShouldBeNil)
  44. m.ServeHTTP(resp, req)
  45. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  46. So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
  47. So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`)
  48. })
  49. Convey("Render JSON with prefix", t, func() {
  50. m := Classic()
  51. prefix := ")]}',\n"
  52. m.Use(Renderer(RenderOptions{
  53. PrefixJSON: []byte(prefix),
  54. }))
  55. m.Get("/foobar", func(r Render) {
  56. r.JSON(300, Greeting{"hello", "world"})
  57. })
  58. resp := httptest.NewRecorder()
  59. req, err := http.NewRequest("GET", "/foobar", nil)
  60. So(err, ShouldBeNil)
  61. m.ServeHTTP(resp, req)
  62. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  63. So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
  64. So(resp.Body.String(), ShouldEqual, prefix+`{"one":"hello","two":"world"}`)
  65. })
  66. Convey("Render Indented JSON", t, func() {
  67. m := Classic()
  68. m.Use(Renderer(RenderOptions{
  69. IndentJSON: true,
  70. }))
  71. m.Get("/foobar", func(r Render) {
  72. r.JSON(300, Greeting{"hello", "world"})
  73. })
  74. resp := httptest.NewRecorder()
  75. req, err := http.NewRequest("GET", "/foobar", nil)
  76. So(err, ShouldBeNil)
  77. m.ServeHTTP(resp, req)
  78. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  79. So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
  80. So(resp.Body.String(), ShouldEqual, `{
  81. "one": "hello",
  82. "two": "world"
  83. }`)
  84. })
  85. Convey("Render JSON and return string", t, func() {
  86. m := Classic()
  87. m.Use(Renderer())
  88. m.Get("/foobar", func(r Render) {
  89. result, err := r.JSONString(Greeting{"hello", "world"})
  90. So(err, ShouldBeNil)
  91. So(result, ShouldEqual, `{"one":"hello","two":"world"}`)
  92. })
  93. resp := httptest.NewRecorder()
  94. req, err := http.NewRequest("GET", "/foobar", nil)
  95. So(err, ShouldBeNil)
  96. m.ServeHTTP(resp, req)
  97. })
  98. Convey("Render with charset JSON", t, func() {
  99. m := Classic()
  100. m.Use(Renderer(RenderOptions{
  101. Charset: "foobar",
  102. }))
  103. m.Get("/foobar", func(r Render) {
  104. r.JSON(300, Greeting{"hello", "world"})
  105. })
  106. resp := httptest.NewRecorder()
  107. req, err := http.NewRequest("GET", "/foobar", nil)
  108. So(err, ShouldBeNil)
  109. m.ServeHTTP(resp, req)
  110. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  111. So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=foobar")
  112. So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`)
  113. })
  114. }
  115. func Test_Render_XML(t *testing.T) {
  116. Convey("Render XML", t, func() {
  117. m := Classic()
  118. m.Use(Renderer())
  119. m.Get("/foobar", func(r Render) {
  120. r.XML(300, GreetingXML{One: "hello", Two: "world"})
  121. })
  122. resp := httptest.NewRecorder()
  123. req, err := http.NewRequest("GET", "/foobar", nil)
  124. So(err, ShouldBeNil)
  125. m.ServeHTTP(resp, req)
  126. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  127. So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
  128. So(resp.Body.String(), ShouldEqual, `<greeting one="hello" two="world"></greeting>`)
  129. })
  130. Convey("Render XML with prefix", t, func() {
  131. m := Classic()
  132. prefix := ")]}',\n"
  133. m.Use(Renderer(RenderOptions{
  134. PrefixXML: []byte(prefix),
  135. }))
  136. m.Get("/foobar", func(r Render) {
  137. r.XML(300, GreetingXML{One: "hello", Two: "world"})
  138. })
  139. resp := httptest.NewRecorder()
  140. req, err := http.NewRequest("GET", "/foobar", nil)
  141. So(err, ShouldBeNil)
  142. m.ServeHTTP(resp, req)
  143. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  144. So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
  145. So(resp.Body.String(), ShouldEqual, prefix+`<greeting one="hello" two="world"></greeting>`)
  146. })
  147. Convey("Render Indented XML", t, func() {
  148. m := Classic()
  149. m.Use(Renderer(RenderOptions{
  150. IndentXML: true,
  151. }))
  152. m.Get("/foobar", func(r Render) {
  153. r.XML(300, GreetingXML{One: "hello", Two: "world"})
  154. })
  155. resp := httptest.NewRecorder()
  156. req, err := http.NewRequest("GET", "/foobar", nil)
  157. So(err, ShouldBeNil)
  158. m.ServeHTTP(resp, req)
  159. So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
  160. So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
  161. So(resp.Body.String(), ShouldEqual, `<greeting one="hello" two="world"></greeting>`)
  162. })
  163. }
  164. func Test_Render_HTML(t *testing.T) {
  165. Convey("Render HTML", t, func() {
  166. m := Classic()
  167. m.Use(Renderers(RenderOptions{
  168. Directory: "fixtures/basic",
  169. }, "fixtures/basic2"))
  170. m.Get("/foobar", func(r Render) {
  171. r.HTML(200, "hello", "jeremy")
  172. r.SetTemplatePath("", "fixtures/basic2")
  173. })
  174. m.Get("/foobar2", func(r Render) {
  175. if r.HasTemplateSet("basic2") {
  176. r.HTMLSet(200, "basic2", "hello", "jeremy")
  177. }
  178. })
  179. resp := httptest.NewRecorder()
  180. req, err := http.NewRequest("GET", "/foobar", nil)
  181. So(err, ShouldBeNil)
  182. m.ServeHTTP(resp, req)
  183. So(resp.Code, ShouldEqual, http.StatusOK)
  184. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  185. So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
  186. resp = httptest.NewRecorder()
  187. req, err = http.NewRequest("GET", "/foobar2", nil)
  188. So(err, ShouldBeNil)
  189. m.ServeHTTP(resp, req)
  190. So(resp.Code, ShouldEqual, http.StatusOK)
  191. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  192. So(resp.Body.String(), ShouldEqual, "<h1>What's up, jeremy</h1>")
  193. Convey("Change render templates path", func() {
  194. resp := httptest.NewRecorder()
  195. req, err := http.NewRequest("GET", "/foobar", nil)
  196. So(err, ShouldBeNil)
  197. m.ServeHTTP(resp, req)
  198. So(resp.Code, ShouldEqual, http.StatusOK)
  199. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  200. So(resp.Body.String(), ShouldEqual, "<h1>What's up, jeremy</h1>")
  201. })
  202. })
  203. Convey("Render HTML and return string", t, func() {
  204. m := Classic()
  205. m.Use(Renderers(RenderOptions{
  206. Directory: "fixtures/basic",
  207. }, "basic2:fixtures/basic2"))
  208. m.Get("/foobar", func(r Render) {
  209. result, err := r.HTMLString("hello", "jeremy")
  210. So(err, ShouldBeNil)
  211. So(result, ShouldEqual, "<h1>Hello jeremy</h1>")
  212. })
  213. m.Get("/foobar2", func(r Render) {
  214. result, err := r.HTMLSetString("basic2", "hello", "jeremy")
  215. So(err, ShouldBeNil)
  216. So(result, ShouldEqual, "<h1>What's up, jeremy</h1>")
  217. })
  218. resp := httptest.NewRecorder()
  219. req, err := http.NewRequest("GET", "/foobar", nil)
  220. So(err, ShouldBeNil)
  221. m.ServeHTTP(resp, req)
  222. resp = httptest.NewRecorder()
  223. req, err = http.NewRequest("GET", "/foobar2", nil)
  224. So(err, ShouldBeNil)
  225. m.ServeHTTP(resp, req)
  226. })
  227. Convey("Render with nested HTML", t, func() {
  228. m := Classic()
  229. m.Use(Renderer(RenderOptions{
  230. Directory: "fixtures/basic",
  231. }))
  232. m.Get("/foobar", func(r Render) {
  233. r.HTML(200, "admin/index", "jeremy")
  234. })
  235. resp := httptest.NewRecorder()
  236. req, err := http.NewRequest("GET", "/foobar", nil)
  237. So(err, ShouldBeNil)
  238. m.ServeHTTP(resp, req)
  239. So(resp.Code, ShouldEqual, http.StatusOK)
  240. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  241. So(resp.Body.String(), ShouldEqual, "<h1>Admin jeremy</h1>")
  242. })
  243. Convey("Render bad HTML", t, func() {
  244. m := Classic()
  245. m.Use(Renderer(RenderOptions{
  246. Directory: "fixtures/basic",
  247. }))
  248. m.Get("/foobar", func(r Render) {
  249. r.HTML(200, "nope", nil)
  250. })
  251. resp := httptest.NewRecorder()
  252. req, err := http.NewRequest("GET", "/foobar", nil)
  253. So(err, ShouldBeNil)
  254. m.ServeHTTP(resp, req)
  255. So(resp.Code, ShouldEqual, http.StatusInternalServerError)
  256. So(resp.Body.String(), ShouldEqual, "html/template: \"nope\" is undefined\n")
  257. })
  258. Convey("Invalid template set", t, func() {
  259. Convey("Empty template set argument", func() {
  260. defer func() {
  261. So(recover(), ShouldNotBeNil)
  262. }()
  263. m := Classic()
  264. m.Use(Renderers(RenderOptions{
  265. Directory: "fixtures/basic",
  266. }, ""))
  267. })
  268. Convey("Bad template set path", func() {
  269. defer func() {
  270. So(recover(), ShouldNotBeNil)
  271. }()
  272. m := Classic()
  273. m.Use(Renderers(RenderOptions{
  274. Directory: "fixtures/basic",
  275. }, "404"))
  276. })
  277. })
  278. }
  279. func Test_Render_XHTML(t *testing.T) {
  280. Convey("Render XHTML", t, func() {
  281. m := Classic()
  282. m.Use(Renderer(RenderOptions{
  283. Directory: "fixtures/basic",
  284. HTMLContentType: ContentXHTML,
  285. }))
  286. m.Get("/foobar", func(r Render) {
  287. r.HTML(200, "hello", "jeremy")
  288. })
  289. resp := httptest.NewRecorder()
  290. req, err := http.NewRequest("GET", "/foobar", nil)
  291. So(err, ShouldBeNil)
  292. m.ServeHTTP(resp, req)
  293. So(resp.Code, ShouldEqual, http.StatusOK)
  294. So(resp.Header().Get(ContentType), ShouldEqual, ContentXHTML+"; charset=UTF-8")
  295. So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
  296. })
  297. }
  298. func Test_Render_Extensions(t *testing.T) {
  299. Convey("Render with extensions", t, func() {
  300. m := Classic()
  301. m.Use(Renderer(RenderOptions{
  302. Directory: "fixtures/basic",
  303. Extensions: []string{".tmpl", ".html"},
  304. }))
  305. m.Get("/foobar", func(r Render) {
  306. r.HTML(200, "hypertext", nil)
  307. })
  308. resp := httptest.NewRecorder()
  309. req, err := http.NewRequest("GET", "/foobar", nil)
  310. So(err, ShouldBeNil)
  311. m.ServeHTTP(resp, req)
  312. So(resp.Code, ShouldEqual, http.StatusOK)
  313. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  314. So(resp.Body.String(), ShouldEqual, "Hypertext!")
  315. })
  316. }
  317. func Test_Render_Funcs(t *testing.T) {
  318. Convey("Render with functions", t, func() {
  319. m := Classic()
  320. m.Use(Renderer(RenderOptions{
  321. Directory: "fixtures/custom_funcs",
  322. Funcs: []template.FuncMap{
  323. {
  324. "myCustomFunc": func() string {
  325. return "My custom function"
  326. },
  327. },
  328. },
  329. }))
  330. m.Get("/foobar", func(r Render) {
  331. r.HTML(200, "index", "jeremy")
  332. })
  333. resp := httptest.NewRecorder()
  334. req, err := http.NewRequest("GET", "/foobar", nil)
  335. So(err, ShouldBeNil)
  336. m.ServeHTTP(resp, req)
  337. So(resp.Body.String(), ShouldEqual, "My custom function")
  338. })
  339. }
  340. func Test_Render_Layout(t *testing.T) {
  341. Convey("Render with layout", t, func() {
  342. m := Classic()
  343. m.Use(Renderer(RenderOptions{
  344. Directory: "fixtures/basic",
  345. Layout: "layout",
  346. }))
  347. m.Get("/foobar", func(r Render) {
  348. r.HTML(200, "content", "jeremy")
  349. })
  350. resp := httptest.NewRecorder()
  351. req, err := http.NewRequest("GET", "/foobar", nil)
  352. So(err, ShouldBeNil)
  353. m.ServeHTTP(resp, req)
  354. So(resp.Body.String(), ShouldEqual, "head<h1>jeremy</h1>foot")
  355. })
  356. Convey("Render with current layout", t, func() {
  357. m := Classic()
  358. m.Use(Renderer(RenderOptions{
  359. Directory: "fixtures/basic",
  360. Layout: "current_layout",
  361. }))
  362. m.Get("/foobar", func(r Render) {
  363. r.HTML(200, "content", "jeremy")
  364. })
  365. resp := httptest.NewRecorder()
  366. req, err := http.NewRequest("GET", "/foobar", nil)
  367. So(err, ShouldBeNil)
  368. m.ServeHTTP(resp, req)
  369. So(resp.Body.String(), ShouldEqual, "content head<h1>jeremy</h1>content foot")
  370. })
  371. Convey("Render with override layout", t, func() {
  372. m := Classic()
  373. m.Use(Renderer(RenderOptions{
  374. Directory: "fixtures/basic",
  375. Layout: "layout",
  376. }))
  377. m.Get("/foobar", func(r Render) {
  378. r.HTML(200, "content", "jeremy", HTMLOptions{
  379. Layout: "another_layout",
  380. })
  381. })
  382. resp := httptest.NewRecorder()
  383. req, err := http.NewRequest("GET", "/foobar", nil)
  384. So(err, ShouldBeNil)
  385. m.ServeHTTP(resp, req)
  386. So(resp.Code, ShouldEqual, http.StatusOK)
  387. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  388. So(resp.Body.String(), ShouldEqual, "another head<h1>jeremy</h1>another foot")
  389. })
  390. }
  391. func Test_Render_Delimiters(t *testing.T) {
  392. Convey("Render with delimiters", t, func() {
  393. m := Classic()
  394. m.Use(Renderer(RenderOptions{
  395. Delims: Delims{"{[{", "}]}"},
  396. Directory: "fixtures/basic",
  397. }))
  398. m.Get("/foobar", func(r Render) {
  399. r.HTML(200, "delims", "jeremy")
  400. })
  401. resp := httptest.NewRecorder()
  402. req, err := http.NewRequest("GET", "/foobar", nil)
  403. So(err, ShouldBeNil)
  404. m.ServeHTTP(resp, req)
  405. So(resp.Code, ShouldEqual, http.StatusOK)
  406. So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
  407. So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
  408. })
  409. }
  410. func Test_Render_BinaryData(t *testing.T) {
  411. Convey("Render binary data", t, func() {
  412. m := Classic()
  413. m.Use(Renderer())
  414. m.Get("/foobar", func(r Render) {
  415. r.RawData(200, []byte("hello there"))
  416. })
  417. m.Get("/foobar2", func(r Render) {
  418. r.RenderData(200, []byte("hello there"))
  419. })
  420. resp := httptest.NewRecorder()
  421. req, err := http.NewRequest("GET", "/foobar", nil)
  422. So(err, ShouldBeNil)
  423. m.ServeHTTP(resp, req)
  424. So(resp.Code, ShouldEqual, http.StatusOK)
  425. So(resp.Header().Get(ContentType), ShouldEqual, ContentBinary)
  426. So(resp.Body.String(), ShouldEqual, "hello there")
  427. resp = httptest.NewRecorder()
  428. req, err = http.NewRequest("GET", "/foobar2", nil)
  429. So(err, ShouldBeNil)
  430. m.ServeHTTP(resp, req)
  431. So(resp.Code, ShouldEqual, http.StatusOK)
  432. So(resp.Header().Get(ContentType), ShouldEqual, CONTENT_PLAIN)
  433. So(resp.Body.String(), ShouldEqual, "hello there")
  434. })
  435. Convey("Render binary data with mime type", t, func() {
  436. m := Classic()
  437. m.Use(Renderer())
  438. m.Get("/foobar", func(r Render) {
  439. r.RW().Header().Set(ContentType, "image/jpeg")
  440. r.RawData(200, []byte("..jpeg data.."))
  441. })
  442. resp := httptest.NewRecorder()
  443. req, err := http.NewRequest("GET", "/foobar", nil)
  444. So(err, ShouldBeNil)
  445. m.ServeHTTP(resp, req)
  446. So(resp.Code, ShouldEqual, http.StatusOK)
  447. So(resp.Header().Get(ContentType), ShouldEqual, "image/jpeg")
  448. So(resp.Body.String(), ShouldEqual, "..jpeg data..")
  449. })
  450. }
  451. func Test_Render_Status(t *testing.T) {
  452. Convey("Render with status 204", t, func() {
  453. resp := httptest.NewRecorder()
  454. r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
  455. r.Status(204)
  456. So(resp.Code, ShouldEqual, http.StatusNoContent)
  457. })
  458. Convey("Render with status 404", t, func() {
  459. resp := httptest.NewRecorder()
  460. r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
  461. r.Error(404)
  462. So(resp.Code, ShouldEqual, http.StatusNotFound)
  463. })
  464. Convey("Render with status 500", t, func() {
  465. resp := httptest.NewRecorder()
  466. r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
  467. r.Error(500)
  468. So(resp.Code, ShouldEqual, http.StatusInternalServerError)
  469. })
  470. }
  471. func Test_Render_NoRace(t *testing.T) {
  472. Convey("Make sure render has no race", t, func() {
  473. m := Classic()
  474. m.Use(Renderer(RenderOptions{
  475. Directory: "fixtures/basic",
  476. }))
  477. m.Get("/foobar", func(r Render) {
  478. r.HTML(200, "hello", "world")
  479. })
  480. done := make(chan bool)
  481. doreq := func() {
  482. resp := httptest.NewRecorder()
  483. req, _ := http.NewRequest("GET", "/foobar", nil)
  484. m.ServeHTTP(resp, req)
  485. done <- true
  486. }
  487. // Run two requests to check there is no race condition
  488. go doreq()
  489. go doreq()
  490. <-done
  491. <-done
  492. })
  493. }
  494. func Test_GetExt(t *testing.T) {
  495. Convey("Get extension", t, func() {
  496. So(GetExt("test"), ShouldBeBlank)
  497. So(GetExt("test.tmpl"), ShouldEqual, ".tmpl")
  498. So(GetExt("test.go.tmpl"), ShouldEqual, ".go.tmpl")
  499. })
  500. }