DataBinding.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. /*
  2. * This source file is part of RmlUi, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://github.com/mikke89/RmlUi
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. * Copyright (c) 2019-2023 The RmlUi Team, and contributors
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. *
  27. */
  28. #include "../Common/TestsShell.h"
  29. #include <RmlUi/Core/Context.h>
  30. #include <RmlUi/Core/DataModelHandle.h>
  31. #include <RmlUi/Core/Element.h>
  32. #include <RmlUi/Core/ElementDocument.h>
  33. #include <cmath>
  34. #include <doctest.h>
  35. using namespace Rml;
  36. namespace {
  37. static const String data_binding_rml = R"(
  38. <rml>
  39. <head>
  40. <title>Test</title>
  41. <link type="text/template" href="/assets/window.rml"/>
  42. <style>
  43. body.window
  44. {
  45. left: 50px;
  46. right: 50px;
  47. top: 30px;
  48. bottom: 30px;
  49. max-width: none;
  50. max-height: none;
  51. }
  52. div#content
  53. {
  54. text-align: left;
  55. padding: 50px;
  56. box-sizing: border-box;
  57. }
  58. </style>
  59. </head>
  60. <body template="window">
  61. <div data-model="basics">
  62. <input type="text" data-value="i0"/>
  63. <h1>Globals</h1>
  64. <p>{{ i0 }}</p>
  65. <p>{{ i1 }}</p>
  66. <p>{{ i2 }}</p>
  67. <p>{{ i3 }}</p>
  68. <p>{{ s0 }}</p>
  69. <p>{{ s1 }}</p>
  70. <p>{{ s2.val }}</p>
  71. <p>{{ s3.val }}</p>
  72. <p>{{ s4.val }}</p>
  73. <p>{{ s5.val }}</p>
  74. <p id="simple">{{ simple }}</p>
  75. <p id="simple_custom">{{ simple_custom }}</p>
  76. <p id="scoped">{{ scoped }}</p>
  77. <p id="scoped_custom">{{ scoped_custom }}</p>
  78. <h1>Basic</h1>
  79. <p>{{ basic.a }}</p>
  80. <p>{{ basic.b }}</p>
  81. <p>{{ basic.c }}</p>
  82. <p>{{ basic.d }}</p>
  83. <p>{{ basic.e }}</p>
  84. <p>{{ basic.f }}</p>
  85. <p id="simple" data-event-click="basic.simple = 2">{{ basic.simple }}</p>
  86. <p>{{ basic.scoped }}</p>
  87. <h1>Wrapped</h1>
  88. <p>{{ wrapped.a.val }}</p>
  89. <p>{{ wrapped.b.val }}</p>
  90. <p>{{ wrapped.c.val }}</p>
  91. <p>{{ wrapped.d.val }}</p>
  92. <p>{{ wrapped.e.val }}</p>
  93. <h1>Pointed</h1>
  94. <p>{{ pointed.a.val }}</p>
  95. <p>{{ pointed.b.val }}</p>
  96. <p>{{ pointed.c.val }}</p>
  97. <h1>Arrays</h1>
  98. <p><span data-for="arrays.a">{{ it }} </span></p>
  99. <p><span data-for="arrays.b">{{ it }} </span></p>
  100. <p><span data-for="arrays.c">{{ it.val }} </span></p>
  101. <p><span data-for="arrays.d">{{ it.val }} </span></p>
  102. <p><span data-for="arrays.e">{{ it.val }} </span></p>
  103. </div>
  104. </body>
  105. </rml>
  106. )";
  107. static const String inside_string_rml = R"(
  108. <rml>
  109. <head>
  110. <title>Test</title>
  111. <link type="text/template" href="/assets/window.rml"/>
  112. <style>
  113. body.window
  114. {
  115. left: 50px;
  116. right: 50px;
  117. top: 30px;
  118. bottom: 30px;
  119. max-width: -1px;
  120. max-height: -1px;
  121. }
  122. </style>
  123. </head>
  124. <body template="window">
  125. <div data-model="basics">
  126. <p>{{ i0 }}</p>
  127. <p>{{ 'i0' }}</p>
  128. <p>{{ 'i{}23' }}</p>
  129. <p>before {{ 'i{{test}}23' }} test</p>
  130. <p>a {{ 'i' }} b {{ 'j' }} c</p>
  131. <p>{{i0}}</p>
  132. <p>{{ 'i{}' }}</p>
  133. </div>
  134. </body>
  135. </rml>
  136. )";
  137. static const String aliasing_rml = R"(
  138. <rml>
  139. <head>
  140. <title>Test</title>
  141. <link type="text/rcss" href="/assets/rml.rcss"/>
  142. <link type="text/rcss" href="/assets/invader.rcss"/>
  143. <link type="text/template" href="/../Tests/Data/UnitTests/data-title.rml"/>
  144. <style>
  145. body {
  146. width: 600px;
  147. height: 400px;
  148. background: #ccc;
  149. color: #333;
  150. }
  151. .title-wrapper { border: 1dp red; }
  152. .icon { width: 64dp; height: 64dp; display: inline-block; }
  153. .icon[icon="a"] { decorator: image("/assets/high_scores_alien_1.tga"); }
  154. .icon[icon="b"] { decorator: image("/assets/high_scores_alien_2.tga"); }
  155. </style>
  156. </head>
  157. <body data-model="basics">
  158. <p>{{ i0 }}</p>
  159. <p data-alias-differentname="i0">{{ differentname }}</p>
  160. <div data-alias-title="s0" data-alias-icon="wrapped.a.val" id="w1">
  161. <template src="data-title"/>
  162. </div>
  163. <div data-alias-title="s1" data-alias-icon="wrapped.b.val" id="w2">
  164. <template src="data-title"/>
  165. </div>
  166. </body>
  167. </rml>
  168. )";
  169. static const String dynamic_rml = R"(
  170. <rml>
  171. <head>
  172. <title>Test</title>
  173. <link type="text/rcss" href="/assets/rml.rcss"/>
  174. <link type="text/rcss" href="/assets/invader.rcss"/>
  175. <link type="text/template" href="/../Tests/Data/UnitTests/data-title.rml"/>
  176. </head>
  177. <body data-model="basics">
  178. <p>{{ arrays.a[0] }}</p>
  179. <p>{{ arrays.a[i0] }}</p>
  180. <p>{{ arrays.b[i1] }}</p>
  181. <p>{{ arrays.c[arrays.b[i1] - 19].val }}</p>
  182. <p>{{ arrays.c[sqrt(arrays.b[i1] - 12) - 1].val }}</p>
  183. </body>
  184. </rml>
  185. )";
  186. struct StringWrap {
  187. StringWrap(String val = "wrap_default") : val(val) {}
  188. String val;
  189. };
  190. enum SimpleEnum { Simple_Zero = 0, Simple_One, Simple_Two };
  191. enum SimpleEnumCustom { Simple_Zero_Custom, Simple_One_Custom, Simple_Two_Custom };
  192. enum class ScopedEnum : uint64_t { Zero = 0, One, Two };
  193. enum class ScopedEnumCustom { Zero, One, Two };
  194. struct Globals {
  195. int i0 = 0;
  196. int* i1 = new int(1);
  197. UniquePtr<int> i2 = MakeUnique<int>(2);
  198. SharedPtr<int> i3 = MakeShared<int>(3);
  199. SimpleEnum simple = Simple_One;
  200. SimpleEnumCustom simple_custom = Simple_One_Custom;
  201. ScopedEnum scoped = ScopedEnum::One;
  202. ScopedEnumCustom scoped_custom = ScopedEnumCustom::One;
  203. String s0 = "s0";
  204. String* s1 = new String("s1");
  205. StringWrap s2 = StringWrap("s2");
  206. StringWrap* s3 = new StringWrap("s3");
  207. UniquePtr<StringWrap> s4 = MakeUnique<StringWrap>("s4");
  208. SharedPtr<StringWrap> s5 = MakeShared<StringWrap>("s5");
  209. // Invalid
  210. const int x0 = 100; // Invalid: const variable
  211. const int* x1 = new int(101); // Invalid: const pointer
  212. UniquePtr<const int> x2 = MakeUnique<int>(102); // Invalid: const pointer
  213. const StringWrap* x3 = new StringWrap("x2"); // Invalid: const pointer
  214. UniquePtr<const StringWrap> x4 = MakeUnique<StringWrap>("x3"); // Invalid: const pointer
  215. } globals;
  216. struct Basic {
  217. int a = 1;
  218. int* b = new int(2);
  219. SimpleEnum simple = Simple_One;
  220. ScopedEnum scoped = ScopedEnum::One;
  221. int GetC()
  222. {
  223. static int v = 5;
  224. return v;
  225. }
  226. int& GetD()
  227. {
  228. static int v = 5;
  229. return v;
  230. }
  231. int* GetE()
  232. {
  233. static int v = 6;
  234. return &v;
  235. }
  236. UniquePtr<int> GetF() { return MakeUnique<int>(7); }
  237. // Invalid: const member
  238. const int x0 = 2;
  239. // Invalid: const pointer
  240. const int* x1 = new int(3);
  241. // Invalid: const qualified member function
  242. int GetX2() const { return 4; }
  243. // Invalid: const reference return
  244. const int& GetX3()
  245. {
  246. static int g = 7;
  247. return g;
  248. }
  249. // Invalid: const pointer return
  250. const int* GetX4()
  251. {
  252. static int h = 8;
  253. return &h;
  254. }
  255. // Invalid: Illegal signature
  256. int GetX5(int) { return 9; }
  257. };
  258. struct Wrapped {
  259. StringWrap a = {"a"};
  260. StringWrap* b = new StringWrap("b");
  261. UniquePtr<StringWrap> c = MakeUnique<StringWrap>("c");
  262. StringWrap& GetD()
  263. {
  264. static StringWrap v = {"e"};
  265. return v;
  266. }
  267. StringWrap* GetE()
  268. {
  269. static StringWrap v = {"f"};
  270. return &v;
  271. }
  272. // Invalid: const pointer
  273. const StringWrap* x0 = new StringWrap("x0");
  274. // Invalid (run-time): Returning non-scalar variable by value.
  275. StringWrap GetX1() { return {"x1"}; }
  276. // Invalid (run-time): Returning non-scalar variable by value.
  277. UniquePtr<StringWrap> GetX2() { return MakeUnique<StringWrap>("x2"); }
  278. };
  279. using StringWrapPtr = UniquePtr<StringWrap>;
  280. struct Pointed {
  281. StringWrapPtr a = MakeUnique<StringWrap>("a");
  282. StringWrapPtr& GetB()
  283. {
  284. static StringWrapPtr v = MakeUnique<StringWrap>("b");
  285. return v;
  286. }
  287. StringWrapPtr* GetC()
  288. {
  289. static StringWrapPtr v = MakeUnique<StringWrap>("c");
  290. return &v;
  291. }
  292. // Invalid: We disallow recursive pointer types (pointer to pointer)
  293. StringWrapPtr* x0 = new StringWrapPtr(new StringWrap("x0"));
  294. // Invalid (run-time error): Only scalar data members can be returned by value
  295. StringWrapPtr GetX1() { return MakeUnique<StringWrap>("x1"); }
  296. };
  297. struct Arrays {
  298. Vector<int> a = {10, 11, 12};
  299. Vector<int*> b = {new int(20), new int(21), new int(22)};
  300. Vector<StringWrap> c = {StringWrap("c1"), StringWrap("c2"), StringWrap("c3")};
  301. Vector<StringWrap*> d = {new StringWrap("d1"), new StringWrap("d2"), new StringWrap("d3")};
  302. Vector<StringWrapPtr> e;
  303. // Invalid: const pointer
  304. Vector<const int*> x0 = {new int(30), new int(31), new int(32)};
  305. // Invalid: const pointer
  306. Vector<UniquePtr<const StringWrap>> x1;
  307. Arrays()
  308. {
  309. e.emplace_back(MakeUnique<StringWrap>("e1"));
  310. e.emplace_back(MakeUnique<StringWrap>("e2"));
  311. e.emplace_back(MakeUnique<StringWrap>("e3"));
  312. x1.emplace_back(MakeUnique<StringWrap>("x1_1"));
  313. x1.emplace_back(MakeUnique<StringWrap>("x1_2"));
  314. x1.emplace_back(MakeUnique<StringWrap>("x1_3"));
  315. }
  316. };
  317. DataModelHandle model_handle;
  318. bool InitializeDataBindings(Context* context)
  319. {
  320. Rml::DataModelConstructor constructor = context->CreateDataModel("basics");
  321. if (!constructor)
  322. return false;
  323. constructor.RegisterTransformFunc("sqrt", [](const VariantList& params) {
  324. if (params.empty())
  325. return Variant();
  326. return Variant(std::sqrt(params[0].Get<int>()));
  327. });
  328. if (auto handle = constructor.RegisterStruct<StringWrap>())
  329. {
  330. handle.RegisterMember("val", &StringWrap::val);
  331. }
  332. constructor.RegisterScalar<SimpleEnumCustom>(
  333. [](const SimpleEnumCustom& value, Rml::Variant& variant) {
  334. if (value == Simple_Zero_Custom)
  335. {
  336. variant = "Zero";
  337. }
  338. else if (value == Simple_One_Custom)
  339. {
  340. variant = "One";
  341. }
  342. else if (value == Simple_Two_Custom)
  343. {
  344. variant = "Two";
  345. }
  346. else
  347. {
  348. Rml::Log::Message(Rml::Log::LT_ERROR, "Invalid value for SimpleEnumCustom type.");
  349. }
  350. },
  351. [](SimpleEnumCustom& value, const Rml::Variant& variant) {
  352. Rml::String str = variant.Get<Rml::String>();
  353. if (str == "Zero")
  354. {
  355. value = Simple_Zero_Custom;
  356. }
  357. else if (str == "One")
  358. {
  359. value = Simple_One_Custom;
  360. }
  361. else if (str == "Two")
  362. {
  363. value = Simple_Two_Custom;
  364. }
  365. else
  366. {
  367. Rml::Log::Message(Rml::Log::LT_ERROR, "Can't convert '%s' to SimpleEnumCustom.", str.c_str());
  368. }
  369. });
  370. constructor.RegisterScalar<ScopedEnumCustom>(
  371. [](const ScopedEnumCustom& value, Rml::Variant& variant) {
  372. if (value == ScopedEnumCustom::Zero)
  373. {
  374. variant = "Zero";
  375. }
  376. else if (value == ScopedEnumCustom::One)
  377. {
  378. variant = "One";
  379. }
  380. else if (value == ScopedEnumCustom::Two)
  381. {
  382. variant = "Two";
  383. }
  384. else
  385. {
  386. Rml::Log::Message(Rml::Log::LT_ERROR, "Invalid value for ScopedEnumCustom type.");
  387. }
  388. },
  389. [](ScopedEnumCustom& value, const Rml::Variant& variant) {
  390. Rml::String str = variant.Get<Rml::String>();
  391. if (str == "Zero")
  392. {
  393. value = ScopedEnumCustom::Zero;
  394. }
  395. else if (str == "One")
  396. {
  397. value = ScopedEnumCustom::One;
  398. }
  399. else if (str == "Two")
  400. {
  401. value = ScopedEnumCustom::Two;
  402. }
  403. else
  404. {
  405. Rml::Log::Message(Rml::Log::LT_ERROR, "Can't convert '%s' to ScopedEnumCustom.", str.c_str());
  406. }
  407. });
  408. {
  409. // Globals
  410. constructor.Bind("i0", &globals.i0);
  411. constructor.Bind("i1", &globals.i1);
  412. constructor.Bind("i2", &globals.i2);
  413. constructor.Bind("i3", &globals.i3);
  414. constructor.Bind("s0", &globals.s0);
  415. constructor.Bind("s1", &globals.s1);
  416. constructor.Bind("s2", &globals.s2);
  417. constructor.Bind("s3", &globals.s3);
  418. constructor.Bind("s4", &globals.s4);
  419. constructor.Bind("s5", &globals.s5);
  420. constructor.Bind("simple", &globals.simple);
  421. constructor.Bind("simple_custom", &globals.simple_custom);
  422. constructor.Bind("scoped", &globals.scoped);
  423. constructor.Bind("scoped_custom", &globals.scoped_custom);
  424. // Invalid: Each of the following should give a compile-time failure.
  425. // constructor.Bind("x0", &globals.x0);
  426. // constructor.Bind("x1", &globals.x1);
  427. // constructor.Bind("x2", &globals.x2);
  428. // constructor.Bind("x3", &globals.x3);
  429. // constructor.Bind("x4", &globals.x4);
  430. }
  431. if (auto handle = constructor.RegisterStruct<Basic>())
  432. {
  433. handle.RegisterMember("a", &Basic::a);
  434. handle.RegisterMember("b", &Basic::b);
  435. handle.RegisterMember("c", &Basic::GetC);
  436. handle.RegisterMember("d", &Basic::GetD);
  437. handle.RegisterMember("e", &Basic::GetE);
  438. handle.RegisterMember("f", &Basic::GetF);
  439. handle.RegisterMember("simple", &Basic::simple);
  440. handle.RegisterMember("scoped", &Basic::scoped);
  441. // handle.RegisterMember("x0", &Basic::x0);
  442. // handle.RegisterMember("x1", &Basic::x1);
  443. // handle.RegisterMember("x2", &Basic::GetX2);
  444. // handle.RegisterMember("x3", &Basic::GetX3);
  445. // handle.RegisterMember("x4", &Basic::GetX4);
  446. // handle.RegisterMember("x5", &Basic::GetX5);
  447. }
  448. constructor.Bind("basic", new Basic);
  449. if (auto handle = constructor.RegisterStruct<Wrapped>())
  450. {
  451. handle.RegisterMember("a", &Wrapped::a);
  452. handle.RegisterMember("b", &Wrapped::b);
  453. handle.RegisterMember("c", &Wrapped::c);
  454. handle.RegisterMember("d", &Wrapped::GetD);
  455. handle.RegisterMember("e", &Wrapped::GetE);
  456. // handle.RegisterMember("x0", &Wrapped::x0);
  457. // handle.RegisterMember("x1", &Wrapped::GetX1);
  458. // handle.RegisterMember("x2", &Wrapped::GetX2);
  459. }
  460. constructor.Bind("wrapped", new Wrapped);
  461. if (auto handle = constructor.RegisterStruct<Pointed>())
  462. {
  463. handle.RegisterMember("a", &Pointed::a);
  464. handle.RegisterMember("b", &Pointed::GetB);
  465. handle.RegisterMember("c", &Pointed::GetC);
  466. // handle.RegisterMember("x0", &Pointed::x0);
  467. // handle.RegisterMember("x1", &Pointed::GetX1);
  468. }
  469. constructor.Bind("pointed", new Pointed);
  470. constructor.RegisterArray<decltype(Arrays::a)>();
  471. constructor.RegisterArray<decltype(Arrays::b)>();
  472. constructor.RegisterArray<decltype(Arrays::c)>();
  473. constructor.RegisterArray<decltype(Arrays::d)>();
  474. constructor.RegisterArray<decltype(Arrays::e)>();
  475. // constructor.RegisterArray<decltype(Arrays::x0)>();
  476. // constructor.RegisterArray<decltype(Arrays::x1)>();
  477. if (auto handle = constructor.RegisterStruct<Arrays>())
  478. {
  479. handle.RegisterMember("a", &Arrays::a);
  480. handle.RegisterMember("b", &Arrays::b);
  481. handle.RegisterMember("c", &Arrays::c);
  482. handle.RegisterMember("d", &Arrays::d);
  483. handle.RegisterMember("e", &Arrays::e);
  484. // handle.RegisterMember("x0", &Arrays::x0);
  485. // handle.RegisterMember("x1", &Arrays::x1);
  486. }
  487. constructor.Bind("arrays", new Arrays);
  488. model_handle = constructor.GetModelHandle();
  489. return true;
  490. }
  491. } // Anonymous namespace
  492. TEST_CASE("data_binding")
  493. {
  494. Context* context = TestsShell::GetContext();
  495. REQUIRE(context);
  496. REQUIRE(InitializeDataBindings(context));
  497. ElementDocument* document = context->LoadDocumentFromMemory(data_binding_rml);
  498. REQUIRE(document);
  499. document->Show();
  500. TestsShell::RenderLoop();
  501. Element* element = document->GetElementById("simple");
  502. CHECK(element->GetInnerRML() == "1");
  503. element = document->GetElementById("simple_custom");
  504. CHECK(element->GetInnerRML() == "One");
  505. element = document->GetElementById("scoped");
  506. CHECK(element->GetInnerRML() == "1");
  507. element = document->GetElementById("scoped_custom");
  508. CHECK(element->GetInnerRML() == "One");
  509. document->Close();
  510. TestsShell::ShutdownShell();
  511. }
  512. TEST_CASE("data_binding.inside_string")
  513. {
  514. Context* context = TestsShell::GetContext();
  515. REQUIRE(context);
  516. REQUIRE(InitializeDataBindings(context));
  517. ElementDocument* document = context->LoadDocumentFromMemory(inside_string_rml);
  518. REQUIRE(document);
  519. document->Show();
  520. TestsShell::RenderLoop();
  521. CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "before i{{test}}23 test");
  522. CHECK(document->QuerySelector("p:nth-child(5)")->GetInnerRML() == "a i b j c");
  523. document->Close();
  524. TestsShell::ShutdownShell();
  525. }
  526. TEST_CASE("data_binding.aliasing")
  527. {
  528. Context* context = TestsShell::GetContext();
  529. REQUIRE(context);
  530. REQUIRE(InitializeDataBindings(context));
  531. ElementDocument* document = context->LoadDocumentFromMemory(aliasing_rml);
  532. REQUIRE(document);
  533. document->Show();
  534. TestsShell::RenderLoop();
  535. CHECK(document->QuerySelector("p:nth-child(1)")->GetInnerRML() == document->QuerySelector("p:nth-child(2)")->GetInnerRML());
  536. CHECK(document->QuerySelector("#w1 .title")->GetInnerRML() == "s0");
  537. CHECK(document->QuerySelector("#w1 .icon")->GetAttribute("icon", String()) == "a");
  538. CHECK(document->QuerySelector("#w2 .title")->GetInnerRML() == "s1");
  539. CHECK(document->QuerySelector("#w2 .icon")->GetAttribute("icon", String()) == "b");
  540. document->Close();
  541. TestsShell::ShutdownShell();
  542. }
  543. TEST_CASE("data_binding.dynamic_variables")
  544. {
  545. Context* context = TestsShell::GetContext();
  546. REQUIRE(context);
  547. REQUIRE(InitializeDataBindings(context));
  548. ElementDocument* document = context->LoadDocumentFromMemory(dynamic_rml);
  549. REQUIRE(document);
  550. document->Show();
  551. TestsShell::RenderLoop();
  552. CHECK(document->QuerySelector("p:nth-child(1)")->GetInnerRML() == "10");
  553. CHECK(document->QuerySelector("p:nth-child(2)")->GetInnerRML() == "10");
  554. CHECK(document->QuerySelector("p:nth-child(3)")->GetInnerRML() == "21");
  555. CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "c3");
  556. CHECK(document->QuerySelector("p:nth-child(5)")->GetInnerRML() == "c3");
  557. *globals.i1 = 0;
  558. context->GetDataModel("basics").GetModelHandle().DirtyVariable("i1");
  559. TestsShell::RenderLoop();
  560. CHECK(document->QuerySelector("p:nth-child(3)")->GetInnerRML() == "20");
  561. CHECK(document->QuerySelector("p:nth-child(4)")->GetInnerRML() == "c2");
  562. document->Close();
  563. *globals.i1 = 1;
  564. TestsShell::ShutdownShell();
  565. }
  566. static const String set_enum_rml = R"(
  567. <rml>
  568. <head>
  569. <title>Test</title>
  570. <link type="text/template" href="/assets/window.rml"/>
  571. <style>
  572. body.window {
  573. width: 500px;
  574. height: 400px;
  575. }
  576. </style>
  577. </head>
  578. <body template="window">
  579. <div data-model="basics">
  580. <p id="simple" data-event-click="simple = 2">{{ simple }}</p>
  581. </div>
  582. </body>
  583. </rml>
  584. )";
  585. TEST_CASE("data_binding.set_enum")
  586. {
  587. Context* context = TestsShell::GetContext();
  588. REQUIRE(context);
  589. globals.simple = Simple_One;
  590. REQUIRE(InitializeDataBindings(context));
  591. ElementDocument* document = context->LoadDocumentFromMemory(set_enum_rml);
  592. REQUIRE(document);
  593. document->Show();
  594. TestsShell::RenderLoop();
  595. Element* element = document->GetElementById("simple");
  596. CHECK(element->GetInnerRML() == "1");
  597. element->DispatchEvent(EventId::Click, Dictionary());
  598. TestsShell::RenderLoop();
  599. CHECK(globals.simple == Simple_Two);
  600. CHECK(element->GetInnerRML() == "2");
  601. document->Close();
  602. TestsShell::ShutdownShell();
  603. }
  604. static const String data_model_on_body_rml = R"(
  605. <rml>
  606. <head>
  607. <title>Test</title>
  608. <link type="text/rcss" href="/assets/rml.rcss"/>
  609. <link type="text/rcss" href="/assets/invader.rcss"/>
  610. <link type="text/template" href="/assets/window.rml"/>
  611. <style>
  612. body {
  613. width: 500px;
  614. height: 400px;
  615. background: #ccc;
  616. color: #333;
  617. }
  618. </style>
  619. </head>
  620. <body BODY_ATTRIBUTE>
  621. <div DIV_ATTRIBUTE>
  622. <div id="simple">{{ simple }}</div>
  623. <div id="s2_val">{{ s2.val }}</div>
  624. <div id="array_size">{{ arrays.a.size }}</div>
  625. <div id="array_empty" data-attr-empty="arrays.a.size == 0"></div>
  626. <div data-for="value, i : arrays.a">{{ i }}: {{ value }}</div>
  627. </div>
  628. </body>
  629. </rml>
  630. )";
  631. TEST_CASE("data_binding.data_model_on_body")
  632. {
  633. Context* context = TestsShell::GetContext();
  634. REQUIRE(context);
  635. REQUIRE(InitializeDataBindings(context));
  636. const String data_model_attribute = R"( data-model="basics")";
  637. const String template_attribute = R"( template="window")";
  638. String document_rml;
  639. SUBCASE("data_model_on_div")
  640. {
  641. document_rml = StringUtilities::Replace(data_model_on_body_rml, "BODY_ATTRIBUTE", "");
  642. document_rml = StringUtilities::Replace(document_rml, "DIV_ATTRIBUTE", data_model_attribute);
  643. }
  644. SUBCASE("data_model_on_body")
  645. {
  646. document_rml = StringUtilities::Replace(data_model_on_body_rml, "BODY_ATTRIBUTE", data_model_attribute);
  647. document_rml = StringUtilities::Replace(document_rml, "DIV_ATTRIBUTE", "");
  648. }
  649. SUBCASE("data_model_on_body_with_template")
  650. {
  651. document_rml = StringUtilities::Replace(data_model_on_body_rml, "BODY_ATTRIBUTE", data_model_attribute + template_attribute);
  652. document_rml = StringUtilities::Replace(document_rml, "DIV_ATTRIBUTE", "");
  653. }
  654. ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
  655. REQUIRE(document);
  656. document->Show();
  657. TestsShell::RenderLoop();
  658. CHECK(document->GetElementById("simple")->GetInnerRML() == Rml::ToString(int(globals.simple)));
  659. CHECK(document->GetElementById("s2_val")->GetInnerRML() == globals.s2.val);
  660. const Vector<int> array = Arrays{}.a;
  661. CHECK(document->GetElementById("array_size")->GetInnerRML() == Rml::ToString(array.size()));
  662. CHECK(document->GetElementById("array_empty")->GetAttribute<bool>("empty", true) == array.empty());
  663. Element* element = document->GetElementById("array_empty")->GetNextElementSibling();
  664. size_t i = 0;
  665. for (; i < array.size() && element; ++i)
  666. {
  667. CHECK(element->GetInnerRML() == Rml::CreateString("%zu: %d", i, array[i]));
  668. element = element->GetNextElementSibling();
  669. }
  670. CHECK(i == array.size());
  671. document->Close();
  672. TestsShell::ShutdownShell();
  673. }