page_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. package web_test
  2. import (
  3. "bytes"
  4. "context"
  5. "io/ioutil"
  6. "mime/multipart"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. . "github.com/qor5/web"
  11. "github.com/qor5/web/multipartestutils"
  12. h "github.com/theplant/htmlgo"
  13. "github.com/theplant/htmltestingutils"
  14. "github.com/theplant/testingutils"
  15. "goji.io"
  16. "goji.io/pat"
  17. )
  18. type User struct {
  19. Name string
  20. Address *Address
  21. }
  22. type Address struct {
  23. Zipcode string
  24. City string
  25. }
  26. func runEvent(
  27. eventFunc EventFunc,
  28. renderChanger func(ctx *EventContext, pr *PageResponse),
  29. eventFormChanger func(builder *multipartestutils.Builder),
  30. ) (indexResp *bytes.Buffer, eventResp *bytes.Buffer) {
  31. pb := New()
  32. var f = func(ctx *EventContext) (r EventResponse, err error) {
  33. r.Reload = true
  34. return
  35. }
  36. if eventFunc != nil {
  37. f = eventFunc
  38. }
  39. var p = pb.Page(func(ctx *EventContext) (pr PageResponse, err error) {
  40. if renderChanger != nil {
  41. renderChanger(ctx, &pr)
  42. } else {
  43. pr.Body = h.H1("Hello")
  44. }
  45. return
  46. }).EventFunc("call", f)
  47. r := httptest.NewRequest("GET", "/", nil)
  48. w := httptest.NewRecorder()
  49. p.ServeHTTP(w, r)
  50. indexResp = w.Body
  51. builder := multipartestutils.NewMultipartBuilder().
  52. EventFunc("call")
  53. if eventFormChanger != nil {
  54. eventFormChanger(builder)
  55. }
  56. r = builder.BuildEventFuncRequest()
  57. w = httptest.NewRecorder()
  58. p.ServeHTTP(w, r)
  59. eventResp = w.Body
  60. return
  61. }
  62. func TestFileUpload(t *testing.T) {
  63. type mystate struct {
  64. File1 []*multipart.FileHeader `form:"-"`
  65. }
  66. var uploadFile = func(ctx *EventContext) (r EventResponse, err error) {
  67. s := &mystate{}
  68. ctx.MustUnmarshalForm(s)
  69. ctx.Flash = s
  70. r.Reload = true
  71. return
  72. }
  73. pb := New()
  74. p := pb.Page(func(ctx *EventContext) (pr PageResponse, err error) {
  75. s := &mystate{}
  76. if ctx.Flash != nil {
  77. s = ctx.Flash.(*mystate)
  78. }
  79. var data []byte
  80. if len(s.File1) > 0 {
  81. var mf multipart.File
  82. mf, err = s.File1[0].Open()
  83. if err != nil {
  84. panic(err)
  85. }
  86. data, err = ioutil.ReadAll(mf)
  87. if err != nil {
  88. panic(err)
  89. }
  90. }
  91. pr.Body = h.H1(string(data))
  92. return
  93. }).EventFunc("uploadFile", uploadFile)
  94. b := multipartestutils.NewMultipartBuilder().
  95. EventFunc("uploadFile").
  96. AddReader("File1", "myfile.txt", strings.NewReader("Hello"))
  97. r := b.BuildEventFuncRequest()
  98. w := httptest.NewRecorder()
  99. p.ServeHTTP(w, r)
  100. diff := testingutils.PrettyJsonDiff(`
  101. {
  102. "body": "\n\u003ch1\u003eHello\u003c/h1\u003e\n",
  103. "reload": true,
  104. "pushState": null
  105. }
  106. `, w.Body.String())
  107. if len(diff) > 0 {
  108. t.Error(diff)
  109. }
  110. }
  111. type DummyComp struct {
  112. }
  113. func (dc *DummyComp) MarshalHTML(ctx context.Context) (r []byte, err error) {
  114. r = []byte("<div>hello</div>")
  115. return
  116. }
  117. var eventCases = []struct {
  118. name string
  119. eventFunc EventFunc
  120. renderChanger func(ctx *EventContext, pr *PageResponse)
  121. eventFormChanger func(b *multipartestutils.Builder)
  122. expectedIndexResp string
  123. expectedEventResp string
  124. }{
  125. {
  126. name: "run event reload states",
  127. renderChanger: func(ctx *EventContext, pr *PageResponse) {
  128. s := &User{
  129. Address: &Address{},
  130. }
  131. if ctx.Flash != nil {
  132. s = ctx.Flash.(*User)
  133. }
  134. pr.Body = h.Text(s.Name + " " + s.Address.City)
  135. s.Name = "Felix"
  136. },
  137. eventFunc: func(ctx *EventContext) (r EventResponse, err error) {
  138. s := &User{}
  139. ctx.MustUnmarshalForm(s)
  140. r.Reload = true
  141. s.Name = "Felix1"
  142. s.Address = &Address{City: "Hangzhou"}
  143. ctx.Flash = s
  144. return
  145. },
  146. expectedEventResp: `{
  147. "body": "Felix1 Hangzhou",
  148. "reload": true,
  149. "pushState": null
  150. }
  151. `,
  152. },
  153. {
  154. name: "render body in event func",
  155. eventFunc: func(ctx *EventContext) (r EventResponse, err error) {
  156. r.Body = h.Div(
  157. h.H1("hello"),
  158. )
  159. return
  160. },
  161. expectedEventResp: `{
  162. "body": "\n\u003cdiv\u003e\n\u003ch1\u003ehello\u003c/h1\u003e\n\u003c/div\u003e\n",
  163. "pushState": null
  164. }`,
  165. },
  166. {
  167. name: "case 1",
  168. renderChanger: func(ctx *EventContext, pr *PageResponse) {
  169. pr.Body = h.RawHTML("<h1>Hello</h1>")
  170. },
  171. expectedEventResp: `
  172. {
  173. "body": "\u003ch1\u003eHello\u003c/h1\u003e",
  174. "reload": true,
  175. "pushState": null
  176. }
  177. `,
  178. },
  179. {
  180. name: "case 2",
  181. renderChanger: func(ctx *EventContext, pr *PageResponse) {
  182. ctx.Injector.TailHTMLComponent("mainjs", h.RawHTML("<script src='/assets/main.js'></script>"), false)
  183. pr.Body = &DummyComp{}
  184. },
  185. expectedEventResp: `{
  186. "body": "\u003cdiv\u003ehello\u003c/div\u003e",
  187. "reload": true,
  188. "pushState": null
  189. }`,
  190. expectedIndexResp: `<!DOCTYPE html>
  191. <html>
  192. <head>
  193. <meta charset='utf8'>
  194. <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
  195. </head>
  196. <body class='front'>
  197. <div id='app' v-cloak><div>hello</div></div>
  198. <script src='/assets/main.js'></script></body>
  199. </html>
  200. `,
  201. },
  202. }
  203. func TestEvents(t *testing.T) {
  204. for _, c := range eventCases {
  205. t.Run(c.name, func(t *testing.T) {
  206. indexResp, eventResp := runEvent(c.eventFunc, c.renderChanger, c.eventFormChanger)
  207. var diff string
  208. if len(c.expectedIndexResp) > 0 {
  209. diff = testingutils.PrettyJsonDiff(c.expectedIndexResp, indexResp)
  210. if len(diff) > 0 {
  211. t.Error(c.name, diff)
  212. }
  213. }
  214. if len(c.expectedEventResp) > 0 {
  215. diff = testingutils.PrettyJsonDiff(c.expectedEventResp, eventResp.String())
  216. if len(diff) > 0 {
  217. t.Error(c.name, diff)
  218. }
  219. }
  220. })
  221. }
  222. }
  223. var mountCases = []struct {
  224. name string
  225. method string
  226. path string
  227. bodyFunc func(b *multipartestutils.Builder)
  228. expected string
  229. }{
  230. {
  231. name: "with param get",
  232. method: "GET",
  233. path: "/home/topics/xgb123",
  234. bodyFunc: nil,
  235. expected: `
  236. <div>
  237. <a href="#" v-on:click='$plaid().vars(vars).form(plaidForm).eventFunc("bookmark").go()'>xgb123</a>
  238. <a href="#" v-on:blur='alert(1); $plaid().vars(vars).form(plaidForm).fieldValue("Text1", $event).eventFunc("doIt").go()'>hello</a>
  239. </div>
  240. `,
  241. },
  242. {
  243. name: "with param post",
  244. method: "POST",
  245. path: "/home/topics/xgb123",
  246. bodyFunc: func(b *multipartestutils.Builder) {
  247. b.EventFunc("bookmark")
  248. },
  249. expected: `{"body":"\n\u003ch1\u003exgb123 bookmarked\u003c/h1\u003e\n","pushState":null}`,
  250. },
  251. }
  252. func TestMultiplePagesAndEvents(t *testing.T) {
  253. var topicIndex = func(ctx *EventContext) (r PageResponse, err error) {
  254. r.Body = h.H1("Hello Topic List")
  255. return
  256. }
  257. var bookmark = func(ctx *EventContext) (r EventResponse, err error) {
  258. topicId := pat.Param(ctx.R, "topicID")
  259. r.Body = h.H1(topicId + " bookmarked")
  260. return
  261. }
  262. var topicDetail = func(ctx *EventContext) (r PageResponse, err error) {
  263. // remove to test global event func with web.New().RegisterEventFunc
  264. // ctx.Hub.RegisterEventFunc("bookmark", bookmark)
  265. topicId := pat.Param(ctx.R, "topicID")
  266. r.Body = h.Div(
  267. h.A().Href("#").Text(topicId).
  268. Attr("v-on:click", Plaid().EventFunc("bookmark").Go()),
  269. h.A().Href("#").Text("hello").
  270. Attr("v-on:blur", Plaid().
  271. BeforeScript("alert(1)").
  272. FieldValue("Text1", Var("$event")).
  273. EventFunc("doIt").
  274. Go(),
  275. ),
  276. )
  277. return
  278. }
  279. pb := New()
  280. pb.RegisterEventFunc("bookmark", bookmark)
  281. mux := goji.NewMux()
  282. mux.Handle(pat.New("/home/topics/:topicID"), pb.Page(topicDetail))
  283. mux.Handle(pat.New("/home/topics"), pb.Page(topicIndex))
  284. for _, c := range mountCases {
  285. t.Run(c.name, func(t *testing.T) {
  286. r := httptest.NewRequest(c.method, c.path, nil)
  287. if c.bodyFunc != nil {
  288. b := multipartestutils.NewMultipartBuilder().
  289. PageURL(c.path)
  290. c.bodyFunc(b)
  291. r = b.BuildEventFuncRequest()
  292. }
  293. w := httptest.NewRecorder()
  294. mux.ServeHTTP(w, r)
  295. selector := "#app div"
  296. if c.bodyFunc != nil {
  297. selector = "*"
  298. }
  299. diff := htmltestingutils.PrettyHtmlDiff(w.Body, selector, c.expected)
  300. if len(diff) > 0 {
  301. t.Error(c.name, diff)
  302. }
  303. })
  304. }
  305. }
  306. func TestEventFuncsOnPageAndBuilder(t *testing.T) {
  307. w := httptest.NewRecorder()
  308. r := multipartestutils.NewMultipartBuilder().
  309. EventFunc("g1").BuildEventFuncRequest()
  310. b := New().EventFuncs(
  311. "g1", func(ctx *EventContext) (r EventResponse, err error) {
  312. r.Body = h.H2("G1")
  313. return
  314. },
  315. )
  316. b.Page(func(ctx *EventContext) (r PageResponse, err error) {
  317. r.Body = h.H1("Page")
  318. return
  319. }).EventFuncs(
  320. "e1", func(ctx *EventContext) (r EventResponse, err error) {
  321. r.Body = h.H2("E1")
  322. return
  323. },
  324. ).ServeHTTP(w, r)
  325. if !strings.Contains(w.Body.String(), "G1") {
  326. t.Errorf("wrong response %s", w.Body.String())
  327. }
  328. }
  329. func TestEmbed(t *testing.T) {
  330. pack := JSComponentsPack()
  331. if len(pack) == 0 {
  332. t.Fatal("No embed string")
  333. }
  334. }