fields_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. package integration_test
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "time"
  9. . "github.com/qor5/admin/presets"
  10. "github.com/qor5/web"
  11. "github.com/qor5/web/multipartestutils"
  12. "github.com/sunfmin/reflectutils"
  13. h "github.com/theplant/htmlgo"
  14. "github.com/theplant/testingutils"
  15. "github.com/thoas/go-funk"
  16. )
  17. type Company struct {
  18. Name string
  19. FoundedAt time.Time
  20. }
  21. type Media string
  22. type User struct {
  23. ID int
  24. Int1 int
  25. Float1 float32
  26. String1 string
  27. Bool1 bool
  28. Time1 time.Time
  29. Company *Company
  30. Media1 Media
  31. }
  32. func TestFields(t *testing.T) {
  33. vd := &web.ValidationErrors{}
  34. vd.FieldError("String1", "too small")
  35. ft := NewFieldDefaults(WRITE).Exclude("ID")
  36. ft.FieldType(time.Time{}).ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  37. return h.Div().Class("time-control").
  38. Text(field.Value(obj).(time.Time).Format("2006-01-02")).
  39. Attr(web.VFieldName(field.Name)...)
  40. })
  41. ft.FieldType(Media("")).ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  42. if field.ContextValue("a") == nil {
  43. return h.Text("")
  44. }
  45. return h.Text(field.ContextValue("a").(string) + ", " + field.ContextValue("b").(string))
  46. })
  47. r := httptest.NewRequest("GET", "/hello", nil)
  48. ctx := &web.EventContext{R: r, Flash: vd}
  49. time1 := time.Unix(1567048169, 0)
  50. time1LocalFormat := time1.Local().Format("2006-01-02 15:04:05")
  51. user := &User{
  52. ID: 1,
  53. Int1: 2,
  54. Float1: 23.1,
  55. String1: "hello",
  56. Bool1: true,
  57. Time1: time1,
  58. Company: &Company{
  59. Name: "Company1",
  60. FoundedAt: time.Unix(1567048169, 0),
  61. },
  62. }
  63. mb := New().Model(&User{})
  64. ftRead := NewFieldDefaults(LIST)
  65. var cases = []struct {
  66. name string
  67. toComponentFun func() h.HTMLComponent
  68. expect string
  69. }{
  70. {
  71. name: "Only with additional nested object",
  72. toComponentFun: func() h.HTMLComponent {
  73. return ft.InspectFields(&User{}).
  74. Labels("Int1", "整数1", "Company.Name", "公司名").
  75. Only("Int1", "Float1", "String1", "Bool1", "Time1", "Company.Name", "Company.FoundedAt").
  76. ToComponent(
  77. mb.Info(),
  78. user,
  79. ctx)
  80. },
  81. expect: `
  82. <v-text-field type='number' v-field-name='[plaidForm, "Int1"]' label='整数1' :value='"2"' :disabled='false'></v-text-field>
  83. <v-text-field type='number' v-field-name='[plaidForm, "Float1"]' label='Float1' :value='"23.1"' :disabled='false'></v-text-field>
  84. <v-text-field type='text' v-field-name='[plaidForm, "String1"]' label='String1' :value='"hello"' :error-messages='["too small"]' :disabled='false'></v-text-field>
  85. <v-checkbox v-field-name='[plaidForm, "Bool1"]' label='Bool1' :input-value='true' :disabled='false'></v-checkbox>
  86. <div v-field-name='[plaidForm, "Time1"]' class='time-control'>2019-08-29</div>
  87. <v-text-field type='text' v-field-name='[plaidForm, "Company.Name"]' label='公司名' :value='"Company1"' :disabled='false'></v-text-field>
  88. <div v-field-name='[plaidForm, "Company.FoundedAt"]' class='time-control'>2019-08-29</div>
  89. `,
  90. },
  91. {
  92. name: "Except with file glob pattern",
  93. toComponentFun: func() h.HTMLComponent {
  94. return ft.InspectFields(&User{}).
  95. Except("Bool*").
  96. ToComponent(mb.Info(), user, ctx)
  97. },
  98. expect: `
  99. <v-text-field type='number' v-field-name='[plaidForm, "Int1"]' label='Int1' :value='"2"' :disabled='false'></v-text-field>
  100. <v-text-field type='number' v-field-name='[plaidForm, "Float1"]' label='Float1' :value='"23.1"' :disabled='false'></v-text-field>
  101. <v-text-field type='text' v-field-name='[plaidForm, "String1"]' label='String1' :value='"hello"' :error-messages='["too small"]' :disabled='false'></v-text-field>
  102. <div v-field-name='[plaidForm, "Time1"]' class='time-control'>2019-08-29</div>
  103. `,
  104. },
  105. {
  106. name: "Read Except with file glob pattern",
  107. toComponentFun: func() h.HTMLComponent {
  108. return ftRead.InspectFields(&User{}).
  109. Except("Float*").ToComponent(mb.Info(), user, ctx)
  110. },
  111. expect: `
  112. <td>1</td>
  113. <td>2</td>
  114. <td>hello</td>
  115. <td>true</td>
  116. `,
  117. },
  118. {
  119. name: "Read for a time field",
  120. toComponentFun: func() h.HTMLComponent {
  121. return ftRead.InspectFields(&User{}).
  122. Only("Time1", "Int1").ToComponent(mb.Info(), user, ctx)
  123. },
  124. expect: fmt.Sprintf(`
  125. <td>%s</td>
  126. <td>2</td>
  127. `, time1LocalFormat),
  128. },
  129. {
  130. name: "pass in context",
  131. toComponentFun: func() h.HTMLComponent {
  132. fb := ft.InspectFields(&User{}).
  133. Only("Media1")
  134. fb.Field("Media1").
  135. WithContextValue("a", "context value1").
  136. WithContextValue("b", "context value2")
  137. return fb.ToComponent(mb.Info(), user, ctx)
  138. },
  139. expect: `context value1, context value2`,
  140. },
  141. }
  142. for _, c := range cases {
  143. t.Run(c.name, func(t *testing.T) {
  144. output := h.MustString(c.toComponentFun(), web.WrapEventContext(context.TODO(), ctx))
  145. diff := testingutils.PrettyJsonDiff(c.expect, output)
  146. if len(diff) > 0 {
  147. t.Error(c.name, diff)
  148. }
  149. })
  150. }
  151. }
  152. type Person struct {
  153. Addresses []*Org
  154. }
  155. type Org struct {
  156. Name string
  157. Address Address
  158. PeopleCount int
  159. Departments []*Department
  160. }
  161. type Department struct {
  162. Name string
  163. Employees []*Employee
  164. DBStatus string
  165. }
  166. type Employee struct {
  167. Number int
  168. Address *Address
  169. }
  170. type Address struct {
  171. City string
  172. Detail AddressDetail
  173. }
  174. type AddressDetail struct {
  175. Address1 string
  176. Address2 string
  177. }
  178. func addressHTML(v Address, formKeyPrefix string) string {
  179. return fmt.Sprintf(`<div>
  180. <label class='v-label theme--light text-caption'>Address</label>
  181. <v-card :outlined='true' class='mx-0 mt-1 mb-4 px-4 pb-0 pt-4'>
  182. <v-text-field type='text' v-field-name='[plaidForm, "%sAddress.City"]' label='City' :value='"%s"' :disabled='false'></v-text-field>
  183. <div>
  184. <label class='v-label theme--light text-caption'>Detail</label>
  185. <v-card :outlined='true' class='mx-0 mt-1 mb-4 px-4 pb-0 pt-4'>
  186. <v-text-field type='text' v-field-name='[plaidForm, "%sAddress.Detail.Address1"]' label='Address1' :value='"%s"' :disabled='false'></v-text-field>
  187. <v-text-field type='text' v-field-name='[plaidForm, "%sAddress.Detail.Address2"]' label='Address2' :value='"%s"' :disabled='false'></v-text-field>
  188. </v-card>
  189. </div>
  190. </v-card>
  191. </div>`,
  192. formKeyPrefix, v.City,
  193. formKeyPrefix, v.Detail.Address1,
  194. formKeyPrefix, v.Detail.Address2,
  195. )
  196. }
  197. func TestFieldsBuilder(t *testing.T) {
  198. defaults := NewFieldDefaults(WRITE)
  199. addressFb := NewFieldsBuilder().Model(&Address{}).Defaults(defaults).Only("City", "Detail")
  200. addressDetailFb := NewFieldsBuilder().Model(&AddressDetail{}).Defaults(defaults).Only("Address1", "Address2")
  201. addressFb.Field("Detail").Nested(addressDetailFb)
  202. employeeFbs := NewFieldsBuilder().Model(&Employee{}).Defaults(defaults)
  203. employeeFbs.Field("Number").ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  204. return h.Input(field.FormKey).Type("text").Value(field.StringValue(obj))
  205. })
  206. employeeFbs.Field("Address").Nested(addressFb)
  207. employeeFbs.Field("FakeNumber").ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  208. return h.Input(field.FormKey).Type("text").Value(fmt.Sprintf("900%v", reflectutils.MustGet(obj, "Number")))
  209. }).SetterFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) (err error) {
  210. v := ctx.R.FormValue(field.FormKey)
  211. if v == "" {
  212. return
  213. }
  214. return reflectutils.Set(obj, "Number", "900"+v)
  215. })
  216. deptFbs := NewFieldsBuilder().Model(&Department{}).Defaults(defaults)
  217. deptFbs.Field("Name").ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  218. // [0].Departments[0].Name
  219. // [0].Departments[1].Name
  220. // [1].Departments[0].Name
  221. return h.Input(field.FormKey).Type("text").Value(field.StringValue(obj))
  222. }).SetterFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) (err error) {
  223. reflectutils.Set(obj, field.Name, ctx.R.FormValue(field.FormKey)+"!!!")
  224. // panic("dept name setter")
  225. return
  226. })
  227. deptFbs.Field("Employees").Nested(employeeFbs).ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  228. return h.Div(
  229. field.NestedFieldsBuilder.ToComponentForEach(field, obj.(*Department).Employees, ctx, nil),
  230. h.Button("Add Employee"),
  231. ).Class("employees")
  232. })
  233. fbs := NewFieldsBuilder().Model(&Org{}).Defaults(defaults)
  234. fbs.Field("Name").ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  235. // [0].Name
  236. return h.Input(field.Name).Type("text").Value(field.StringValue(obj))
  237. })
  238. // .SetterFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) (err error) {
  239. // return
  240. // })
  241. fbs.Field("Departments").Nested(deptFbs).ComponentFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) h.HTMLComponent {
  242. // [0].Departments
  243. return h.Div(
  244. field.NestedFieldsBuilder.ToComponentForEach(field, obj.(*Org).Departments, ctx, nil),
  245. h.Button("Add Department"),
  246. ).Class("departments")
  247. })
  248. fbs.Field("Address").Nested(addressFb)
  249. fbs.Field("PeopleCount").SetterFunc(func(obj interface{}, field *FieldContext, ctx *web.EventContext) (err error) {
  250. reflectutils.Set(obj, field.Name, ctx.R.FormValue(field.FormKey))
  251. return
  252. })
  253. var toComponentCases = []struct {
  254. name string
  255. obj *Org
  256. setup func(ctx *web.EventContext)
  257. expectedHTML string
  258. }{
  259. {
  260. name: "Only deleted",
  261. obj: &Org{
  262. Name: "Name 1",
  263. Address: Address{
  264. City: "c1",
  265. Detail: AddressDetail{
  266. Address1: "addr1",
  267. Address2: "addr2",
  268. },
  269. },
  270. Departments: []*Department{
  271. {
  272. Name: "11111",
  273. Employees: []*Employee{
  274. {Number: 111},
  275. {Number: 222},
  276. {Number: 333},
  277. },
  278. },
  279. {
  280. Name: "22222",
  281. Employees: []*Employee{
  282. {Number: 333},
  283. {Number: 444},
  284. },
  285. },
  286. },
  287. },
  288. setup: func(ctx *web.EventContext) {
  289. ContextModifiedIndexesBuilder(ctx).
  290. AppendDeleted("Departments[0].Employees", 1).
  291. AppendDeleted("Departments[0].Employees", 5)
  292. },
  293. expectedHTML: fmt.Sprintf(`
  294. <input type='hidden' v-field-name='[plaidForm, "__Deleted.Departments[0].Employees"]' value='1,5'>
  295. <input name='Name' type='text' value='Name 1'>
  296. <div class='departments'>
  297. <input name='Departments[0].Name' type='text' value='11111'>
  298. <div class='employees'>
  299. <input name='Departments[0].Employees[0].Number' type='text' value='111'>
  300. %s
  301. <input name='Departments[0].Employees[0].FakeNumber' type='text' value='900111'>
  302. <input name='Departments[0].Employees[2].Number' type='text' value='333'>
  303. %s
  304. <input name='Departments[0].Employees[2].FakeNumber' type='text' value='900333'>
  305. <button>Add Employee</button>
  306. </div>
  307. <input name='Departments[1].Name' type='text' value='22222'>
  308. <div class='employees'>
  309. <input name='Departments[1].Employees[0].Number' type='text' value='333'>
  310. %s
  311. <input name='Departments[1].Employees[0].FakeNumber' type='text' value='900333'>
  312. <input name='Departments[1].Employees[1].Number' type='text' value='444'>
  313. %s
  314. <input name='Departments[1].Employees[1].FakeNumber' type='text' value='900444'>
  315. <button>Add Employee</button>
  316. </div>
  317. <button>Add Department</button>
  318. </div>
  319. %s
  320. <v-text-field type='number' v-field-name='[plaidForm, "PeopleCount"]' label='People Count' :value='"0"' :disabled='false'></v-text-field>
  321. `,
  322. addressHTML(Address{}, "Departments[0].Employees[0]."),
  323. addressHTML(Address{}, "Departments[0].Employees[2]."),
  324. addressHTML(Address{}, "Departments[1].Employees[0]."),
  325. addressHTML(Address{}, "Departments[1].Employees[1]."),
  326. addressHTML(Address{
  327. City: "c1",
  328. Detail: AddressDetail{
  329. Address1: "addr1",
  330. Address2: "addr2",
  331. },
  332. }, "")),
  333. },
  334. {
  335. name: "Deleted with Sorted",
  336. obj: &Org{
  337. Name: "Name 1",
  338. Departments: []*Department{
  339. {
  340. Name: "11111",
  341. Employees: []*Employee{
  342. {Number: 111},
  343. {Number: 222},
  344. {Number: 333},
  345. {Number: 444},
  346. {Number: 555},
  347. },
  348. },
  349. {
  350. Name: "22222",
  351. Employees: []*Employee{
  352. {Number: 333},
  353. {Number: 444},
  354. },
  355. },
  356. },
  357. },
  358. setup: func(ctx *web.EventContext) {
  359. ContextModifiedIndexesBuilder(ctx).
  360. AppendDeleted("Departments[0].Employees", 1).
  361. SetSorted("Departments[0].Employees", []string{"2", "0", "3", "6"})
  362. },
  363. expectedHTML: fmt.Sprintf(`
  364. <input type='hidden' v-field-name='[plaidForm, "__Deleted.Departments[0].Employees"]' value='1'>
  365. <input type='hidden' v-field-name='[plaidForm, "__Sorted.Departments[0].Employees"]' value='2,0,3,6'>
  366. <input name='Name' type='text' value='Name 1'>
  367. <div class='departments'>
  368. <input name='Departments[0].Name' type='text' value='11111'>
  369. <div class='employees'>
  370. <input name='Departments[0].Employees[2].Number' type='text' value='333'>
  371. %s
  372. <input name='Departments[0].Employees[2].FakeNumber' type='text' value='900333'>
  373. <input name='Departments[0].Employees[0].Number' type='text' value='111'>
  374. %s
  375. <input name='Departments[0].Employees[0].FakeNumber' type='text' value='900111'>
  376. <input name='Departments[0].Employees[3].Number' type='text' value='444'>
  377. %s
  378. <input name='Departments[0].Employees[3].FakeNumber' type='text' value='900444'>
  379. <input name='Departments[0].Employees[4].Number' type='text' value='555'>
  380. %s
  381. <input name='Departments[0].Employees[4].FakeNumber' type='text' value='900555'>
  382. <button>Add Employee</button>
  383. </div>
  384. <input name='Departments[1].Name' type='text' value='22222'>
  385. <div class='employees'>
  386. <input name='Departments[1].Employees[0].Number' type='text' value='333'>
  387. %s
  388. <input name='Departments[1].Employees[0].FakeNumber' type='text' value='900333'>
  389. <input name='Departments[1].Employees[1].Number' type='text' value='444'>
  390. %s
  391. <input name='Departments[1].Employees[1].FakeNumber' type='text' value='900444'>
  392. <button>Add Employee</button>
  393. </div>
  394. <button>Add Department</button>
  395. </div>
  396. %s
  397. <v-text-field type='number' v-field-name='[plaidForm, "PeopleCount"]' label='People Count' :value='"0"' :disabled='false'></v-text-field>
  398. `,
  399. addressHTML(Address{}, "Departments[0].Employees[2]."),
  400. addressHTML(Address{}, "Departments[0].Employees[0]."),
  401. addressHTML(Address{}, "Departments[0].Employees[3]."),
  402. addressHTML(Address{}, "Departments[0].Employees[4]."),
  403. addressHTML(Address{}, "Departments[1].Employees[0]."),
  404. addressHTML(Address{}, "Departments[1].Employees[1]."),
  405. addressHTML(Address{}, ""),
  406. ),
  407. },
  408. }
  409. for _, c := range toComponentCases {
  410. t.Run(c.name, func(t *testing.T) {
  411. ctx := &web.EventContext{
  412. R: httptest.NewRequest("POST", "/", nil),
  413. }
  414. c.setup(ctx)
  415. result := fbs.ToComponent(nil, c.obj, ctx)
  416. actual1 := h.MustString(result, context.TODO())
  417. diff := testingutils.PrettyJsonDiff(c.expectedHTML, actual1)
  418. if diff != "" {
  419. t.Error(diff)
  420. }
  421. })
  422. }
  423. var unmarshalCases = []struct {
  424. name string
  425. initial *Org
  426. expected *Org
  427. req *http.Request
  428. removeDeletedAndSort bool
  429. }{
  430. {
  431. name: "case with deleted",
  432. initial: &Org{
  433. Departments: []*Department{
  434. {
  435. Name: "Department A",
  436. Employees: []*Employee{
  437. {
  438. Number: 0,
  439. },
  440. {
  441. Number: 1,
  442. },
  443. },
  444. },
  445. {
  446. Name: "Department B",
  447. Employees: []*Employee{
  448. {Number: 0},
  449. {Number: 1},
  450. {Number: 2},
  451. },
  452. },
  453. {
  454. Name: "Department C",
  455. },
  456. },
  457. },
  458. req: multipartestutils.NewMultipartBuilder().
  459. AddField("Name", "Org 1").
  460. AddField("PeopleCount", "420").
  461. AddField("Departments[1].Name", "Department 1").
  462. AddField("Departments[1].Employees[0].Number", "888").
  463. AddField("Departments[1].Employees[2].Number", "999").
  464. AddField("Departments[1].Employees[1].FakeNumber", "666").
  465. AddField("Departments[1].DBStatus", "Verified").
  466. AddField("Departments[2].Name", "Department C").
  467. AddField("__Deleted.Departments[0].Employees", "1,2").
  468. AddField("__Deleted.Departments[1].Employees", "0").
  469. AddField("__Deleted.Departments[2].Employees", "0").
  470. BuildEventFuncRequest(),
  471. removeDeletedAndSort: false,
  472. expected: &Org{
  473. Name: "Org 1",
  474. PeopleCount: 420,
  475. Departments: []*Department{
  476. {
  477. Name: "!!!",
  478. Employees: []*Employee{
  479. {
  480. Number: 0,
  481. },
  482. nil,
  483. nil,
  484. },
  485. },
  486. {
  487. Name: "Department 1!!!",
  488. Employees: []*Employee{
  489. nil,
  490. {
  491. Number: 900666,
  492. Address: &Address{},
  493. },
  494. {
  495. Number: 999,
  496. Address: &Address{},
  497. },
  498. },
  499. },
  500. {
  501. Name: "Department C!!!",
  502. Employees: []*Employee{
  503. nil,
  504. },
  505. },
  506. },
  507. },
  508. },
  509. {
  510. name: "removeDeletedAndSort true",
  511. initial: &Org{
  512. Departments: []*Department{
  513. {
  514. Name: "Department A",
  515. Employees: []*Employee{
  516. {
  517. Number: 0,
  518. },
  519. {
  520. Number: 1,
  521. },
  522. },
  523. },
  524. {
  525. Name: "Department B",
  526. Employees: []*Employee{
  527. {Number: 0},
  528. {Number: 1},
  529. {Number: 2},
  530. },
  531. },
  532. {
  533. Name: "Department C",
  534. },
  535. },
  536. },
  537. req: multipartestutils.NewMultipartBuilder().
  538. AddField("Name", "Org 1").
  539. AddField("PeopleCount", "420").
  540. AddField("Departments[1].Name", "Department 1").
  541. AddField("Departments[1].Employees[0].Number", "888").
  542. AddField("Departments[1].Employees[2].Number", "999").
  543. AddField("Departments[1].Employees[1].FakeNumber", "666").
  544. AddField("Departments[1].DBStatus", "Verified").
  545. AddField("__Deleted.Departments[0].Employees", "1,5").
  546. AddField("__Deleted.Departments[1].Employees", "0").
  547. BuildEventFuncRequest(),
  548. removeDeletedAndSort: true,
  549. expected: &Org{
  550. Name: "Org 1",
  551. PeopleCount: 420,
  552. Departments: []*Department{
  553. {
  554. Name: "!!!",
  555. Employees: []*Employee{
  556. {
  557. Number: 0,
  558. },
  559. },
  560. },
  561. {
  562. Name: "Department 1!!!",
  563. Employees: []*Employee{
  564. {
  565. Number: 900666,
  566. Address: &Address{},
  567. },
  568. {
  569. Number: 999,
  570. Address: &Address{},
  571. },
  572. },
  573. },
  574. {
  575. Name: "Department C",
  576. },
  577. },
  578. },
  579. },
  580. {
  581. name: "removeDeletedAndSort true and sorted",
  582. initial: &Org{
  583. Departments: []*Department{
  584. {
  585. Name: "Department A",
  586. Employees: []*Employee{
  587. {
  588. Number: 0,
  589. },
  590. {
  591. Number: 1,
  592. },
  593. {
  594. Number: 2,
  595. },
  596. },
  597. },
  598. {
  599. Name: "Department B",
  600. Employees: []*Employee{
  601. {Number: 0},
  602. {Number: 1},
  603. {Number: 2},
  604. },
  605. },
  606. {
  607. Name: "Department C",
  608. },
  609. },
  610. },
  611. req: multipartestutils.NewMultipartBuilder().
  612. AddField("Name", "Org 1").
  613. AddField("PeopleCount", "420").
  614. AddField("Departments[1].Name", "Department 1").
  615. AddField("Departments[1].Employees[0].Number", "000").
  616. AddField("Departments[1].Employees[2].Number", "222").
  617. AddField("Departments[1].Employees[3].Number", "333").
  618. AddField("Departments[1].Employees[4].Number", "444").
  619. AddField("Departments[1].Employees[1].FakeNumber", "666"). // this will set Number[1] to 900666
  620. AddField("Departments[1].DBStatus", "Verified").
  621. AddField("__Deleted.Departments[0].Employees", "1,5").
  622. AddField("__Sorted.Departments[1].Employees", "3,4,0,2").
  623. AddField("__Deleted.Departments[1].Employees", "0").
  624. BuildEventFuncRequest(),
  625. removeDeletedAndSort: true,
  626. expected: &Org{
  627. Name: "Org 1",
  628. PeopleCount: 420,
  629. Departments: []*Department{
  630. {
  631. Name: "!!!",
  632. Employees: []*Employee{
  633. {
  634. Number: 0,
  635. },
  636. {
  637. Number: 2,
  638. },
  639. },
  640. },
  641. {
  642. Name: "Department 1!!!",
  643. Employees: []*Employee{
  644. {
  645. Number: 333,
  646. Address: &Address{},
  647. },
  648. {
  649. Number: 444,
  650. Address: &Address{},
  651. },
  652. {
  653. Number: 222,
  654. Address: &Address{},
  655. },
  656. {
  657. Number: 900666,
  658. Address: &Address{},
  659. },
  660. },
  661. },
  662. {
  663. Name: "Department C",
  664. },
  665. },
  666. },
  667. },
  668. {
  669. name: "removeDeletedAndSort false and sorted",
  670. initial: &Org{
  671. Departments: []*Department{
  672. {
  673. Name: "Department A",
  674. Employees: []*Employee{
  675. {
  676. Number: 0,
  677. },
  678. {
  679. Number: 1,
  680. },
  681. {
  682. Number: 2,
  683. },
  684. },
  685. },
  686. {
  687. Name: "Department B",
  688. Employees: []*Employee{
  689. {Number: 0},
  690. {Number: 1},
  691. {Number: 2},
  692. },
  693. },
  694. {
  695. Name: "Department C",
  696. },
  697. },
  698. },
  699. req: multipartestutils.NewMultipartBuilder().
  700. AddField("Name", "Org 1").
  701. AddField("PeopleCount", "420").
  702. AddField("Departments[1].Name", "Department 1").
  703. AddField("Departments[1].Employees[0].Number", "000").
  704. AddField("Departments[1].Employees[2].Number", "222").
  705. AddField("Departments[1].Employees[3].Number", "333").
  706. AddField("Departments[1].Employees[4].Number", "444").
  707. AddField("Departments[1].Employees[1].FakeNumber", "666"). // this will set Number[1] to 900666
  708. AddField("Departments[1].DBStatus", "Verified").
  709. AddField("__Deleted.Departments[0].Employees", "1,3").
  710. AddField("__Sorted.Departments[1].Employees", "3,4,0,2").
  711. AddField("__Deleted.Departments[1].Employees", "0").
  712. BuildEventFuncRequest(),
  713. removeDeletedAndSort: false,
  714. expected: &Org{
  715. Name: "Org 1",
  716. PeopleCount: 420,
  717. Departments: []*Department{
  718. {
  719. Name: "!!!",
  720. Employees: []*Employee{
  721. {
  722. Number: 0,
  723. },
  724. nil,
  725. {
  726. Number: 2,
  727. },
  728. nil,
  729. },
  730. },
  731. {
  732. Name: "Department 1!!!",
  733. Employees: []*Employee{
  734. nil,
  735. {
  736. Number: 900666,
  737. Address: &Address{},
  738. },
  739. {
  740. Number: 222,
  741. Address: &Address{},
  742. },
  743. {
  744. Number: 333,
  745. Address: &Address{},
  746. },
  747. {
  748. Number: 444,
  749. Address: &Address{},
  750. },
  751. },
  752. },
  753. {
  754. Name: "Department C",
  755. },
  756. },
  757. },
  758. },
  759. {
  760. name: "object item",
  761. initial: &Org{
  762. Name: "Org 1",
  763. Address: Address{
  764. City: "org city",
  765. Detail: AddressDetail{
  766. Address1: "org addr1",
  767. Address2: "org addr2",
  768. },
  769. },
  770. Departments: []*Department{
  771. {
  772. Name: "Department A",
  773. Employees: []*Employee{
  774. {
  775. Number: 1,
  776. Address: &Address{
  777. City: "1 city",
  778. Detail: AddressDetail{
  779. Address1: "1 addr1",
  780. Address2: "1 addr2",
  781. },
  782. },
  783. },
  784. {
  785. Number: 2,
  786. },
  787. },
  788. },
  789. },
  790. },
  791. req: multipartestutils.NewMultipartBuilder().
  792. AddField("Name", "Org 1").
  793. AddField("Address.City", "org city e").
  794. AddField("Address.Detail.Address1", "org addr1 e").
  795. AddField("Address.Detail.Address2", "org addr2 e").
  796. AddField("Departments[0].Name", "Department A").
  797. AddField("Departments[0].Employees[0].Number", "1").
  798. AddField("Departments[0].Employees[0].Address.City", "1 city e").
  799. AddField("Departments[0].Employees[0].Address.Detail.Address1", "1 addr1 e").
  800. AddField("Departments[0].Employees[0].Address.Detail.Address2", "1 addr2 e").
  801. AddField("Departments[0].Employees[1].Number", "2").
  802. AddField("Departments[0].Employees[1].Address.City", "2 city").
  803. AddField("Departments[0].Employees[1].Address.Detail.Address1", "2 addr1").
  804. AddField("Departments[0].Employees[1].Address.Detail.Address2", "2 addr2").
  805. BuildEventFuncRequest(),
  806. removeDeletedAndSort: false,
  807. expected: &Org{
  808. Name: "Org 1",
  809. Address: Address{
  810. City: "org city e",
  811. Detail: AddressDetail{
  812. Address1: "org addr1 e",
  813. Address2: "org addr2 e",
  814. },
  815. },
  816. Departments: []*Department{
  817. {
  818. Name: "Department A!!!",
  819. Employees: []*Employee{
  820. {
  821. Number: 1,
  822. Address: &Address{
  823. City: "1 city e",
  824. Detail: AddressDetail{
  825. Address1: "1 addr1 e",
  826. Address2: "1 addr2 e",
  827. },
  828. },
  829. },
  830. {
  831. Number: 2,
  832. Address: &Address{
  833. City: "2 city",
  834. Detail: AddressDetail{
  835. Address1: "2 addr1",
  836. Address2: "2 addr2",
  837. },
  838. },
  839. },
  840. },
  841. },
  842. },
  843. },
  844. },
  845. }
  846. for _, c := range unmarshalCases {
  847. t.Run(c.name, func(t *testing.T) {
  848. ctx2 := &web.EventContext{R: c.req}
  849. _ = ctx2.R.ParseMultipartForm(128 << 20)
  850. actual2 := c.initial
  851. vErr := fbs.Unmarshal(actual2, nil, c.removeDeletedAndSort, ctx2)
  852. if vErr.HaveErrors() {
  853. t.Error(vErr.Error())
  854. }
  855. diff := testingutils.PrettyJsonDiff(c.expected, actual2)
  856. if diff != "" {
  857. t.Error(diff)
  858. }
  859. })
  860. }
  861. }
  862. func TestGoFunk(t *testing.T) {
  863. depts := []*Department{
  864. {Name: "1"},
  865. {Name: "2"},
  866. }
  867. funk.ForEach(depts, func(obj *Department) {
  868. obj.Name = "3"
  869. })
  870. funk.ForEach(depts, func(obj interface{}) {
  871. obj.(*Department).Name = "3"
  872. })
  873. }