on xenial help sign in
More test cleanup and additional tests
commit1b19c7b4bb7dbfc9cb6490213f7085bae263b2b3 authorMichael Terry <michael.terry@canonical.com> dateTue 20 Jan 2015, 18:47 (12 years ago) parent8dc759a
11 files changed+295−102
qml/Greeter/Greeter.qml+4−7 qml/Greeter/LoginList.qml+5−1 qml/Shell.qml+1 tests/plugins/LightDM/CMakeLists.txt+15 tests/plugins/LightDM/usersmodel.cpp+84 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Greeter/tst_MultiGreeter.qml−40 tests/qmltests/Greeter/tst_NarrowView.qml+55−2 tests/qmltests/Greeter/tst_SingleGreeter.qml−49 tests/qmltests/Greeter/tst_WideView.qml+112−3 tests/qmltests/tst_Shell.qml+18
qml/Greeter/Greeter.qml+4−7
@@ -46,6 +46,7 @@ Showable {
4646 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
4747
4848 property bool tabletMode
49+ property url viewSource // only used for testing
4950
5051 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
5152 property int failedLoginsDelayAttempts: 7 // number of failed logins
@@ -120,6 +121,7 @@ Showable {
120121 }
121122
122123 function selectUser(uid) {
124+ d.waiting = true;
123125 currentIndex = uid;
124126 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
125127 AccountsService.user = user;
@@ -183,7 +185,8 @@ Showable {
183185 anchors.fill: parent
184186
185187 active: root.required
186− source: (d.multiUser || tabletMode) ? "WideView.qml" : "NarrowView.qml"
188+ source: root.viewSource ? root.viewSource :
189+ (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
187190
188191 onLoaded: {
189192 loader.item.reset();
@@ -262,12 +265,6 @@ Showable {
262265 value: d.currentIndex
263266 }
264267
265− Binding {
266− target: loader.item
267− property: "currentUser"
268− value: LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole)
269− }
270−
271268 Binding {
272269 target: loader.item
273270 property: "userModel"
qml/Greeter/LoginList.qml+5−1
@@ -49,7 +49,11 @@ Item {
4949 }
5050
5151 function showMessage(html) {
52− infoLabel.text = html;
52+ if (infoLabel.text === "") {
53+ infoLabel.text = html;
54+ } else {
55+ infoLabel.text += "<br>" + html;
56+ }
5357 }
5458
5559 function showPrompt(text, isSecret, isDefaultPrompt) {
qml/Shell.qml+1
@@ -106,6 +106,7 @@ Item {
106106
107107 GSettings {
108108 id: backgroundSettings
109+ objectName: "backgroundSettings"
109110 schema.id: "org.gnome.desktop.background"
110111 }
111112
tests/plugins/LightDM/CMakeLists.txt+15
@@ -4,9 +4,17 @@ add_executable(GreeterDBusTestExec
44 )
55qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)
66
7+add_executable(GreeterUsersModelTestExec
8+ usersmodel.cpp
9+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11+ )
12+qt5_use_modules(GreeterUsersModelTestExec Core Test)
13+
714include_directories(
815 ${CMAKE_CURRENT_BINARY_DIR}
916 ${CMAKE_SOURCE_DIR}/plugins/LightDM
17+ ${CMAKE_SOURCE_DIR}/plugins/Utils
1018 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
1119 ${LIBLIGHTDM_INCLUDE_DIRS}
1220 )
@@ -16,6 +24,13 @@ target_link_libraries(GreeterDBusTestExec
1624 -llightdm-qt5-2
1725 )
1826
27+target_link_libraries(GreeterUsersModelTestExec
28+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full
29+ -llightdm-qt5-2
30+ )
31+
1932add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
2033
2134add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full" MockLightDM-full "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
35+
36+add_binary_qml_test(GreeterUsersModel "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full" MockLightDM-full "")
tests/plugins/LightDM/usersmodel.cppnew file+84
@@ -0,0 +1,84 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "UsersModel.h"
18+
19+#include <QLightDM/UsersModel>
20+#include <QtTest>
21+
22+class GreeterUsersModelTest : public QObject
23+{
24+ Q_OBJECT
25+
26+private:
27+ static int findName(QAbstractItemModel *model, const QString &name)
28+ {
29+ for (int i = 0; i < model->rowCount(QModelIndex()); i++) {
30+ if (model->data(model->index(i, 0), QLightDM::UsersModel::NameRole).toString() == name) {
31+ return i;
32+ }
33+ }
34+ return -1;
35+ }
36+
37+ static QString getStringValue(QAbstractItemModel *model, const QString &name, int role)
38+ {
39+ int i = findName(model, name);
40+ return model->data(model->index(i, 0), role).toString();
41+ }
42+
43+private Q_SLOTS:
44+
45+ void init()
46+ {
47+ model = new UsersModel();
48+ QVERIFY(model);
49+ sourceModel = new QLightDM::UsersModel();
50+ QVERIFY(sourceModel);
51+ }
52+
53+ void cleanup()
54+ {
55+ delete model;
56+ delete sourceModel;
57+ }
58+
59+ void testMangleColor()
60+ {
61+ QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62+ QVERIFY(background == "#dd4814");
63+
64+ background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65+ QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
66+ }
67+
68+ void testMangleEmptyName()
69+ {
70+ QString name = getStringValue(sourceModel, "empty-name", QLightDM::UsersModel::RealNameRole);
71+ QVERIFY(name == "");
72+
73+ name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
74+ QVERIFY(name == "empty-name");
75+ }
76+
77+private:
78+ UsersModel *model;
79+ QLightDM::UsersModel *sourceModel;
80+};
81+
82+QTEST_MAIN(GreeterUsersModelTest)
83+
84+#include "usersmodel.moc"
tests/qmltests/CMakeLists.txt+1
@@ -65,6 +65,7 @@ add_qml_test(Greeter MultiGreeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DI
6565add_qml_test(Greeter SingleGreeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
6666add_qml_test(Greeter Infographics ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
6767add_qml_test(Greeter Clock ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
68+add_qml_test(Greeter Greeter ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
6869add_qml_test(Greeter NarrowView ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
6970add_qml_test(Greeter WideView ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
7071add_qml_test(Launcher Launcher)
tests/qmltests/Greeter/tst_MultiGreeter.qml−40
@@ -323,46 +323,6 @@ Item {
323323 compare(LightDM.Users.data(index, LightDM.UserRoles.BackgroundPathRole), "")
324324 }
325325
326− function test_tappedSignal_data() {
327− return [
328− {tag: "left", posX: units.gu(2)},
329− {tag: "right", posX: greeter.width - units.gu(2)}
330− ]
331− }
332−
333− function test_tappedSignal(data) {
334− select_user("no-password");
335− tappedSpy.clear();
336− tap(greeter, data.posX, greeter.height - units.gu(1))
337− tryCompare(tappedSpy, "count", 1)
338− }
339−
340− function test_teaseLockedUnlocked_data() {
341− return [
342− {tag: "unlocked", locked: false, narrow: false},
343− {tag: "locked", locked: true, narrow: false},
344− ];
345− }
346−
347− function test_teaseLockedUnlocked(data) {
348− tappedSpy.clear()
349− greeter.locked = data.locked;
350−
351− tap(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
352−
353− if (!data.locked || data.narrow) {
354− tappedSpy.wait()
355− tryCompare(tappedSpy, "count", 1);
356− } else {
357− // waiting 100ms to make sure nothing happens
358− wait(100);
359− compare(tappedSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
360− }
361−
362− // Reset value
363− greeter.locked = false;
364− }
365−
366326 function test_dbus_set_active_entry() {
367327 select_user("no-password") // to guarantee a selected signal
368328 selectionSpy.clear()
tests/qmltests/Greeter/tst_NarrowView.qml+55−2
@@ -271,7 +271,7 @@ Item {
271271 }
272272
273273 UT.UnityTestCase {
274− name: "WideView"
274+ name: "NarrowView"
275275 when: windowShown
276276
277277 property Item view: loader.status === Loader.Ready ? loader.item : null
@@ -327,7 +327,7 @@ Item {
327327 {tag: "left", x: 0, offset: 0, count: 1},
328328 {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
329329 {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
330− {tag: "right", x: view.width, offset: 0, count: 1}
330+ {tag: "right", x: view.width, offset: 0, count: 1},
331331 ]
332332 }
333333 function test_tease(data) {
@@ -451,5 +451,58 @@ Item {
451451 // far enough to have it hidden
452452 tryCompare(coverPage, "x", 0);
453453 }
454+
455+ function test_dragToHide_data() {
456+ return [
457+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
458+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
459+ ];
460+ }
461+ function test_dragToHide(data) {
462+ var coverPage = findChild(view, "coverPage");
463+ compare(coverPage.x, 0);
464+ compare(coverPage.visible, true);
465+ compare(coverPage.shown, true);
466+ compare(coverPage.showProgress, 1);
467+ compare(view.fullyShown, true);
468+
469+ touchFlick(view,
470+ data.startX, view.height / 2, // start pos
471+ data.endX, view.height / 2); // end pos
472+
473+ tryCompare(coverPage, "x", data.hiddenX);
474+ tryCompare(coverPage, "visible", false);
475+ tryCompare(coverPage, "shown", false);
476+ tryCompare(coverPage, "showProgress", 0);
477+ compare(view.fullyShown, false);
478+ }
479+
480+ function test_hiddenViewRemainsHiddenAfterResize_data() {
481+ return [
482+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
483+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
484+ ];
485+ }
486+ function test_hiddenViewRemainsHiddenAfterResize(data) {
487+ touchFlick(view,
488+ data.startX, view.height / 2, // start pos
489+ data.endX, view.height / 2); // end pos
490+
491+ var coverPage = findChild(view, "coverPage");
492+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
493+ tryCompare(coverPage, "visible", false);
494+ tryCompare(coverPage, "shown", false);
495+ tryCompare(coverPage, "showProgress", 0);
496+
497+ // flip dimensions to simulate an orientation change
498+ view.width = loader.height;
499+ view.height = loader.width;
500+
501+ // All properties should remain consistent
502+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
503+ tryCompare(coverPage, "visible", false);
504+ tryCompare(coverPage, "shown", false);
505+ tryCompare(coverPage, "showProgress", 0);
506+ }
454507 }
455508}
tests/qmltests/Greeter/tst_SingleGreeter.qml−49
@@ -114,54 +114,5 @@ Rectangle {
114114 selectedSpy.wait();
115115 tryCompare(selectedSpy, "count", 1);
116116 }
117−
118− function test_dragToHide_data() {
119− return [
120− {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1, hiddenX: -greeter.width},
121− {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95, hiddenX: greeter.width},
122− ];
123− }
124− function test_dragToHide(data) {
125− compare(greeter.x, 0);
126− compare(greeter.visible, true);
127− compare(greeter.shown, true);
128− compare(greeter.showProgress, 1);
129−
130− touchFlick(greeter,
131− data.startX, greeter.height / 2, // start pos
132− data.endX, greeter.height / 2); // end pos
133−
134− tryCompare(greeter, "x", data.hiddenX);
135− tryCompare(greeter, "visible", false);
136− tryCompare(greeter, "shown", false);
137− tryCompare(greeter, "showProgress", 0);
138− }
139−
140− function test_hiddenGreeterRemainsHiddenAfterResize_data() {
141− return [
142− {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1},
143− {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95},
144− ];
145− }
146− function test_hiddenGreeterRemainsHiddenAfterResize(data) {
147− touchFlick(greeter,
148− data.startX, greeter.height / 2, // start pos
149− data.endX, greeter.height / 2); // end pos
150−
151− tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
152− tryCompare(greeter, "visible", false);
153− tryCompare(greeter, "shown", false);
154− tryCompare(greeter, "showProgress", 0);
155−
156− // flip dimensions to simulate an orientation change
157− greeter.width = greeterLoader.height;
158− greeter.height = greeterLoader.width;
159−
160− // All properties should remain consistent
161− tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
162− tryCompare(greeter, "visible", false);
163− tryCompare(greeter, "shown", false);
164− tryCompare(greeter, "showProgress", 0);
165− }
166117 }
167118}
tests/qmltests/Greeter/tst_WideView.qml+112−3
@@ -274,6 +274,22 @@ Item {
274274 removeTimeConstraintsFromDirectionalDragAreas(loader.item);
275275 }
276276
277+ function getIndexOf(name) {
278+ for (var i = 0; i < LightDM.Users.count; i++) {
279+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
280+ return i;
281+ }
282+ }
283+ fail("Didn't find name")
284+ return -1;
285+ }
286+
287+ function selectUser(name) {
288+ var i = getIndexOf(name);
289+ view.currentIndex = i;
290+ return i;
291+ }
292+
277293 function swipeAwayCover() {
278294 tryCompare(view, "fullyShown", true);
279295 var touchY = view.height / 2;
@@ -283,9 +299,20 @@ Item {
283299 waitForRendering(view);
284300 }
285301
286− function test_tease() {
287− tap(view, 1, 1);
288− compare(teaseSpy.count, 1);
302+ function test_tease_data() {
303+ return [
304+ {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
305+ {tag: "left", x: 0, offset: 0, count: 1, locked: false},
306+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1, locked: false},
307+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0, locked: false},
308+ {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
309+ ]
310+ }
311+ function test_tease(data) {
312+ view.locked = data.locked;
313+ view.dragHandleLeftMargin = data.offset;
314+ tap(view, data.x, 0);
315+ compare(teaseSpy.count, data.count);
289316 }
290317
291318 function test_selected() {
@@ -300,6 +327,8 @@ Item {
300327 view.locked = true;
301328 view.showPrompt("Prompt", true, true);
302329 var passwordInput = findChild(view, "passwordInput");
330+ compare(passwordInput.placeholderText, "Prompt");
331+ compare(passwordInput.echoMode, TextInput.Password);
303332 tap(passwordInput);
304333 typeString("password");
305334 keyClick(Qt.Key_Enter);
@@ -307,6 +336,19 @@ Item {
307336 compare(respondedSpy.signalArguments[0][0], "password");
308337 }
309338
339+ function test_respondedWithNonSecret() {
340+ view.locked = true;
341+ view.showPrompt("otp", false, false);
342+ var passwordInput = findChild(view, "passwordInput");
343+ compare(passwordInput.placeholderText, "otp");
344+ compare(passwordInput.echoMode, TextInput.Normal);
345+ tap(passwordInput);
346+ typeString("foo");
347+ keyClick(Qt.Key_Enter);
348+ compare(respondedSpy.count, 1);
349+ compare(respondedSpy.signalArguments[0][0], "foo");
350+ }
351+
310352 function test_respondedWithSwipe() {
311353 swipeAwayCover();
312354 compare(respondedSpy.count, 1);
@@ -324,5 +366,72 @@ Item {
324366 swipeAwayCover();
325367 tryCompare(view, "required", false);
326368 }
369+
370+ function test_showMessage() {
371+ view.showMessage("Welcome to Unity Greeter");
372+ view.showMessage("<font color=\"#df382c\">This is an error</font>");
373+ view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
374+ var infoLabel = findChild(view, "infoLabel");
375+ compare(infoLabel.text, "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages and this is a really long message too. wow so long much length");
376+ compare(infoLabel.textFormat, Text.StyledText);
377+ compare(infoLabel.clip, true);
378+ verify(infoLabel.contentWidth > infoLabel.width);
379+ verify(infoLabel.opacity < 1);
380+ tryCompare(infoLabel, "opacity", 1);
381+ }
382+
383+ // Escape is used to reset the authentication, especially if PAM is unresponsive
384+ function test_escape() {
385+ view.currentIndex = 1;
386+ view.locked = true;
387+ view.showPrompt("Prompt", true, true);
388+ var passwordInput = findChild(view, "passwordInput");
389+ tap(passwordInput);
390+ compare(passwordInput.focus, true);
391+ compare(passwordInput.enabled, true);
392+
393+ typeString("password");
394+ keyClick(Qt.Key_Enter);
395+ compare(passwordInput.focus, false);
396+ compare(passwordInput.enabled, false);
397+
398+ compare(selectedSpy.count, 0);
399+ keyClick(Qt.Key_Escape);
400+ compare(passwordInput.focus, false);
401+ compare(passwordInput.enabled, true);
402+ compare(selectedSpy.count, 1);
403+ compare(selectedSpy.signalArguments[0][0], 1);
404+ }
405+
406+ function test_unicode() {
407+ var index = selectUser("unicode");
408+ var label = findChild(view, "username" + index);
409+ tryCompare(label, "text", "가나다라마");
410+ }
411+
412+ function test_longName() {
413+ var index = selectUser("long-name");
414+ var label = findChild(view, "username" + index);
415+ tryCompare(label, "truncated", true);
416+ }
417+
418+ function test_promptless() {
419+ var passwordInput = findChild(view, "passwordInput");
420+
421+ view.locked = true;
422+ compare(passwordInput, "placeholderText", "Retry");
423+ tap(passwordInput);
424+ compare(respondedSpy.count, 0);
425+ compare(selectedSpy.count, 1);
426+ compare(selectedSpy.signalArguments[0][0], 0);
427+ selectedSpy.clear();
428+
429+ view.locked = false;
430+ compare(passwordInput, "placeholderText", "Tap to unlock");
431+ tap(passwordInput);
432+ compare(selectedSpy.count, 0);
433+ compare(respondedSpy.count, 1);
434+ compare(respondedSpy.signalArguments[0][0], "");
435+ }
327436 }
328437}
tests/qmltests/tst_Shell.qml+18
@@ -776,5 +776,23 @@ Item {
776776 var launcherPanel = findChild(shell, "launcherPanel");
777777 tryCompare(launcherPanel, "x", -launcherPanel.width);
778778 }
779+
780+ function test_background_data() {
781+ return [
782+ {tag: "color", accounts: "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>", gsettings: "", output: "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"},
783+ {tag: "empty", accounts: "", gsettings: "", output: ""},
784+ {tag: "as-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: "", output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
785+ {tag: "gs-specified", accounts: "", gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
786+ {tag: "both-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
787+ {tag: "invalid-as", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
788+ {tag: "invalid-both", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"), output: shell.defaultBackground},
789+ ]
790+ }
791+ function test_background(data) {
792+ AccountsService.backgroundFile = data.accounts;
793+ var backgroundSettings = findChild(shell, "backgroundSettings");
794+ backgroundSettings.pictureUri = data.gsettings;
795+ tryCompare(shell.background, data.output);
796+ }
779797 }
780798}