diff options
Diffstat (limited to 'tests/auto/qml')
80 files changed, 1839 insertions, 736 deletions
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index 6302df235d..6b81f4c616 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -34,7 +34,9 @@ add_subdirectory(qqmlerror) add_subdirectory(qqmlincubator) add_subdirectory(qqmlinfo) add_subdirectory(qqmllistreference) -add_subdirectory(qqmllocale) +if(QT_FEATURE_qml_locale) + add_subdirectory(qqmllocale) +endif() add_subdirectory(qqmlmetaobject) if(NOT ANDROID) # QTBUG-100003 add_subdirectory(qqmlmoduleplugin) @@ -46,7 +48,9 @@ add_subdirectory(qqmlpromise) add_subdirectory(qtqmlmodules) add_subdirectory(qquickfolderlistmodel) add_subdirectory(qqmlapplicationengine) -add_subdirectory(qqmlsettings) +if(QT_FEATURE_settings) + add_subdirectory(qqmlsettings) +endif() if(NOT INTEGRITY) # There's no mounted filesystem on INTEGRITY therefore skipping qmldiskcache diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index 2e96de7819..df63e3d48d 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -254,8 +254,6 @@ built-ins/String/prototype/toLocaleLowerCase/special_casing_conditional.js fails built-ins/String/prototype/toLowerCase/Final_Sigma_U180E.js fails built-ins/String/prototype/toLowerCase/special_casing_conditional.js fails built-ins/TypedArray/prototype/constructor.js fails -built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan.js fails -built-ins/TypedArray/prototype/slice/bit-precision.js fails built-ins/TypedArray/prototype/sort/arraylength-internal.js fails built-ins/TypedArray/prototype/sort/comparefn-call-throws.js fails built-ins/TypedArray/prototype/sort/comparefn-calls.js fails diff --git a/tests/auto/qml/ecmascripttests/test262.py b/tests/auto/qml/ecmascripttests/test262.py deleted file mode 100755 index e701d32966..0000000000 --- a/tests/auto/qml/ecmascripttests/test262.py +++ /dev/null @@ -1,611 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2017 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only - -# Copyright 2009 the Sputnik authors. All rights reserved. -# This code is governed by the BSD license found in the LICENSE file. - -# This is derived from sputnik.py, the Sputnik console test runner, -# with elements from packager.py, which is separately -# copyrighted. TODO: Refactor so there is less duplication between -# test262.py and packager.py. - -import sys -from os import path -rootDir = path.dirname(path.realpath(__file__)) -sys.path.insert(0, path.abspath(rootDir + "/test262/tools/packaging")) - -import logging -import optparse -import os -import platform -import re -import subprocess -import tempfile -import time -import xml.dom.minidom -import datetime -import shutil -import json -import stat -import multiprocessing -import signal - - -from parseTestRecord import parseTestRecord, stripHeader - -from packagerConfig import * - -# excluded features that are still experimental and not part of any official standard -# see also the features.txt file in test262/ -excludedFeatures = [ - "BigInt", - "class-fields-public", - "class-fields-private", - "Promise.prototype.finally", - "async-iteration", - "Symbol.asyncIterator", - "object-rest", - "object-spread", - "optional-catch-binding", - "regexp-dotall", - "regexp-lookbehind", - "regexp-named-groups", - "regexp-unicode-property-escapes", - "Atomics", - "SharedArrayBuffer", - "Array.prototype.flatten", - "Array.prototype.flatMap", - "string-trimming", - "String.prototype.trimEnd", - "String.prototype.trimStart", - "numeric-separator-literal", - - # optional features, not supported by us - "caller" -] - -# ############# Helpers needed for parallel multi-process test execution ############ - -def runTest(case, args): - return case.Run(args) - -def runTestVarArgs(args): - return runTest(*args) - -def initWorkerProcess(): - signal.signal(signal.SIGINT, signal.SIG_IGN) - -# ############# - -class Test262Error(Exception): - def __init__(self, message): - self.message = message - -def ReportError(s): - raise Test262Error(s) - - -class TestExpectations: - def __init__(self, enabled): - self.testsToSkip = [] - self.failingTests = [] - f = open(rootDir + "/TestExpectations") - if not enabled: - return - for line in f.read().splitlines(): - line = line.strip() - if len(line) == 0 or line[0] == "#": - continue - record = line.split() - if len(record) == 1: - self.failingTests.append(record[0]) - else: - test = record[0] - expectation = record[1] - if expectation == "skip": - self.testsToSkip.append(test) - f.close() - - def update(self, progress): - unexpectedPasses = [c.case.name for c in progress.failed_tests if c.case.IsNegative()] - - # If a test fails that we expected to fail, then it actually passed unexpectedly. - failures = [c.case.name for c in progress.failed_tests if not c.case.IsNegative()] - for failure in failures: - if failure in self.failingTests: - unexpectedPasses.append(failure) - - f = open(rootDir + "/TestExpectations") - lines = f.read().splitlines() - oldLen = len(lines) - for result in unexpectedPasses: - expectationLine = result - try: - lines.remove(expectationLine) - except ValueError: - pass - - f.close() - if len(lines) != oldLen: - f = open(rootDir + "/TestExpectations", "w") - f.write("\n".join(lines)) - f.close() - print "Changes to TestExpectations written!" - - -if not os.path.exists(EXCLUDED_FILENAME): - print "Cannot generate (JSON) test262 tests without a file," + \ - " %s, showing which tests have been disabled!" % EXCLUDED_FILENAME - sys.exit(1) -EXCLUDE_LIST = xml.dom.minidom.parse(EXCLUDED_FILENAME) -EXCLUDE_LIST = EXCLUDE_LIST.getElementsByTagName("test") -EXCLUDE_LIST = [x.getAttribute("id") for x in EXCLUDE_LIST] - - -def BuildOptions(): - result = optparse.OptionParser() - result.add_option("--command", default="qmljs", help="The command-line to run") - result.add_option("--tests", default=path.abspath(rootDir + '/test262'), - help="Path to the tests") - result.add_option("--cat", default=False, action="store_true", - help="Print packaged test code that would be run") - result.add_option("--summary", default=True, action="store_true", - help="Print summary after running tests") - result.add_option("--full-summary", default=False, action="store_true", - help="Print summary and test output after running tests") - result.add_option("--strict_only", default=False, action="store_true", - help="Test only strict mode") - result.add_option("--non_strict_only", default=False, action="store_true", - help="Test only non-strict mode") - result.add_option("--parallel", default=False, action="store_true", - help="Run tests in parallel") - result.add_option("--with-test-expectations", default=False, action="store_true", - help="Parse TestExpectations to deal with tests known to fail") - result.add_option("--update-expectations", default=False, action="store_true", - help="Update test expectations fail when a test passes that was expected to fail") - # TODO: Once enough tests are made strict compat, change the default - # to "both" - result.add_option("--unmarked_default", default="non_strict", - help="default mode for tests of unspecified strictness") - return result - - -def ValidateOptions(options): - if not options.command: - ReportError("A --command must be specified.") - if not path.exists(options.tests): - ReportError("Couldn't find test path '%s'" % options.tests) - - -placeHolderPattern = re.compile(r"\{\{(\w+)\}\}") - - -def IsWindows(): - p = platform.system() - return (p == 'Windows') or (p == 'Microsoft') - - -class TempFile(object): - - def __init__(self, suffix="", prefix="tmp", text=False): - self.suffix = suffix - self.prefix = prefix - self.text = text - self.fd = None - self.name = None - self.is_closed = False - self.Open() - - def Open(self): - (self.fd, self.name) = tempfile.mkstemp( - suffix = self.suffix, - prefix = self.prefix, - text = self.text) - - def Write(self, str): - os.write(self.fd, str) - - def Read(self): - f = file(self.name) - result = f.read() - f.close() - return result - - def Close(self): - if not self.is_closed: - self.is_closed = True - os.close(self.fd) - - def Dispose(self): - try: - self.Close() - os.unlink(self.name) - except OSError, e: - logging.error("Error disposing temp file: %s", str(e)) - - -class TestResult(object): - - def __init__(self, exit_code, stdout, stderr, case): - self.exit_code = exit_code - self.stdout = stdout - self.stderr = stderr - self.case = case - - def ReportOutcome(self, long_format): - name = self.case.GetName() - mode = self.case.GetMode() - if self.HasUnexpectedOutcome(): - if self.case.IsNegative(): - print "=== %s was expected to fail in %s, but didn't ===" % (name, mode) - else: - if long_format: - print "=== %s failed in %s ===" % (name, mode) - else: - print "%s in %s: " % (name, mode) - out = self.stdout.strip() - if len(out) > 0: - print "--- output ---" - print out - err = self.stderr.strip() - if len(err) > 0: - print "--- errors ---" - print err - if long_format: - print "===" - elif self.case.IsNegative(): - print "%s failed in %s as expected" % (name, mode) - else: - print "%s passed in %s" % (name, mode) - - def HasFailed(self): - return self.exit_code != 0 - - def HasUnexpectedOutcome(self): - if self.case.IsNegative(): - return not self.HasFailed() - else: - return self.HasFailed() - - -class TestCase(object): - - def __init__(self, suite, name, full_path, strict_mode): - self.suite = suite - self.name = name - self.full_path = full_path - self.strict_mode = strict_mode - f = open(self.full_path) - self.contents = f.read() - f.close() - testRecord = parseTestRecord(self.contents, name) - self.test = testRecord["test"] - if 'features' in testRecord: - self.features = testRecord["features"]; - else: - self.features = [] - del testRecord["test"] - del testRecord["header"] - self.testRecord = testRecord; - - - def GetName(self): - return self.name - - def GetMode(self): - if self.strict_mode: - return "strict mode" - else: - return "non-strict mode" - - def GetPath(self): - return self.name - - def NegateResult(self): - if self.IsNegative(): - del self.testRecord['negative'] - else: - self.testRecord['negative'] = "Some failure"; - - def IsNegative(self): - return 'negative' in self.testRecord - - def IsOnlyStrict(self): - return 'onlyStrict' in self.testRecord - - def IsNoStrict(self): - return 'noStrict' in self.testRecord - - def IsExperimental(self): - for f in self.features: - if excludedFeatures.count(f) >= 1: - return True; - return False - - def GetSource(self): - # "var testDescrip = " + str(self.testRecord) + ';\n\n' + \ - source = self.suite.GetInclude("assert.js") + \ - self.suite.GetInclude("sta.js") + \ - self.test + '\n' - if 'includes' in self.testRecord: - for inc in self.testRecord['includes']: - source += self.suite.GetInclude(inc); - - if self.strict_mode: - source = '"use strict";\nvar strict_mode = true;\n' + source - else: - source = "var strict_mode = false; \n" + source - return source - - def InstantiateTemplate(self, template, params): - def GetParameter(match): - key = match.group(1) - return params.get(key, match.group(0)) - return placeHolderPattern.sub(GetParameter, template) - - def Execute(self, command): - if IsWindows(): - args = '%s' % command - else: - args = command.split(" ") - stdout = TempFile(prefix="test262-out-") - stderr = TempFile(prefix="test262-err-") - try: - logging.info("exec: %s", str(args)) - process = subprocess.Popen( - args, - shell = IsWindows(), - stdout = stdout.fd, - stderr = stderr.fd - ) - code = process.wait() - out = stdout.Read() - err = stderr.Read() - finally: - stdout.Dispose() - stderr.Dispose() - return (code, out, err) - - def RunTestIn(self, command_template, tmp): - tmp.Write(self.GetSource()) - tmp.Close() - command = self.InstantiateTemplate(command_template, { - 'path': tmp.name - }) - (code, out, err) = self.Execute(command) - return TestResult(code, out, err, self) - - def Run(self, command_template): - tmp = TempFile(suffix=".js", prefix="test262-", text=True) - try: - result = self.RunTestIn(command_template, tmp) - finally: - tmp.Dispose() - return result - - def Print(self): - print self.GetSource() - - -class ProgressIndicator(object): - - def __init__(self, count): - self.count = count - self.succeeded = 0 - self.failed = 0 - self.failed_tests = [] - - def HasRun(self, result): - result.ReportOutcome(True) - if result.HasUnexpectedOutcome(): - self.failed += 1 - self.failed_tests.append(result) - else: - self.succeeded += 1 - - -def MakePlural(n): - if (n == 1): - return (n, "") - else: - return (n, "s") - - -class TestSuite(object): - - def __init__(self, root, strict_only, non_strict_only, unmarked_default, load_expectations): - # TODO: derive from packagerConfig.py - self.test_root = path.join(root, 'test') - self.lib_root = path.join(root, 'harness') - self.strict_only = strict_only - self.non_strict_only = non_strict_only - self.unmarked_default = unmarked_default - self.include_cache = { } - self.expectations = TestExpectations(load_expectations) - - def IsExcludedTest(self, path): - if path.startswith('annexB'): - return True; - if path.startswith('harness'): - return True; - if path.startswith('intl402'): - return True; - return False; - - def Validate(self): - if not path.exists(self.test_root): - ReportError("No test repository found") - if not path.exists(self.lib_root): - ReportError("No test library found") - - def IsHidden(self, path): - return path.startswith('.') or path == 'CVS' - - def IsTestCase(self, path): - return path.endswith('.js') - - def ShouldRun(self, rel_path, tests): - if len(tests) == 0: - return True - for test in tests: - if test in rel_path: - return True - return False - - def GetInclude(self, name): - if not name in self.include_cache: - static = path.join(self.lib_root, name) - if path.exists(static): - f = open(static) - contents = stripHeader(f.read()) - contents = re.sub(r'\r\n', '\n', contents) - self.include_cache[name] = contents + "\n" - f.close() - else: - ReportError("Can't find: " + static) - return self.include_cache[name] - - def EnumerateTests(self, tests): - logging.info("Listing tests in %s", self.test_root) - cases = [] - for root, dirs, files in os.walk(self.test_root): - for f in [x for x in dirs if self.IsHidden(x)]: - dirs.remove(f) - dirs.sort() - for f in sorted(files): - if self.IsTestCase(f): - full_path = path.join(root, f) - if full_path.startswith(self.test_root): - rel_path = full_path[len(self.test_root)+1:] - else: - logging.warning("Unexpected path %s", full_path) - rel_path = full_path - if self.ShouldRun(rel_path, tests) and not self.IsExcludedTest(rel_path): - basename = path.basename(full_path)[:-3] - name = rel_path.replace('.js', '') - if EXCLUDE_LIST.count(basename) >= 1 or self.expectations.testsToSkip.count(name) >= 1: - print 'Excluded: ' + rel_path - else: - if not self.non_strict_only: - strict_case = TestCase(self, name, full_path, True) - if self.expectations.failingTests.count(name) >= 1: - strict_case.NegateResult() - if not strict_case.IsNoStrict() and not strict_case.IsExperimental(): - if strict_case.IsOnlyStrict() or \ - self.unmarked_default in ['both', 'strict']: - cases.append(strict_case) - if not self.strict_only: - non_strict_case = TestCase(self, name, full_path, False) - if self.expectations.failingTests.count(name) >= 1: - non_strict_case.NegateResult() - if not non_strict_case.IsOnlyStrict() and not non_strict_case.IsExperimental(): - if non_strict_case.IsNoStrict() or \ - self.unmarked_default in ['both', 'non_strict']: - cases.append(non_strict_case) - logging.info("Done listing tests") - return cases - - def PrintSummary(self, progress): - print - print "=== Summary ===" - count = progress.count - succeeded = progress.succeeded - failed = progress.failed - print " - Ran %i test%s" % MakePlural(count) - if progress.failed == 0: - print " - All tests succeeded" - else: - percent = ((100.0 * succeeded) / count,) - print " - Passed %i test%s (%.1f%%)" % (MakePlural(succeeded) + percent) - percent = ((100.0 * failed) / count,) - print " - Failed %i test%s (%.1f%%)" % (MakePlural(failed) + percent) - positive = [c for c in progress.failed_tests if not c.case.IsNegative()] - negative = [c for c in progress.failed_tests if c.case.IsNegative()] - if len(positive) > 0: - print - print "Failed tests" - for result in positive: - print " %s in %s" % (result.case.GetName(), result.case.GetMode()) - if len(negative) > 0: - print - print "Expected to fail but passed ---" - for result in negative: - print " %s in %s" % (result.case.GetName(), result.case.GetMode()) - - def PrintFailureOutput(self, progress): - for result in progress.failed_tests: - print - result.ReportOutcome(False) - - def Run(self, command_template, tests, print_summary, full_summary, parallel, update_expectations): - if not "{{path}}" in command_template: - command_template += " {{path}}" - cases = self.EnumerateTests(tests) - if len(cases) == 0: - ReportError("No tests to run") - progress = ProgressIndicator(len(cases)) - - if parallel: - pool = multiprocessing.Pool(processes=multiprocessing.cpu_count(), initializer=initWorkerProcess) - results = pool.imap_unordered(func=runTestVarArgs, iterable=[(case, command_template) for case in cases], chunksize=multiprocessing.cpu_count() * 8) - for result in results: - progress.HasRun(result) - else: - for case in cases: - result = case.Run(command_template) - progress.HasRun(result) - if print_summary: - self.PrintSummary(progress) - if full_summary: - self.PrintFailureOutput(progress) - else: - print - print "Use --full-summary to see output from failed tests" - print - if update_expectations: - self.expectations.update(progress) - return progress.failed == 0 - - def Print(self, tests): - cases = self.EnumerateTests(tests) - if len(cases) > 0: - cases[0].Print() - - -def Main(): - # Uncomment the next line for more logging info. - #logging.basicConfig(level=logging.DEBUG) - # Some date tests rely on being run in pacific time and the USA's locale: - os.environ["TZ"] = "America/Los_Angeles" # it *matters* that this is (7m8s) *East* of PST's nominal meridian ! - os.environ["LANG"] = "en_US.UTF-8" - os.environ["LC_TIME"] = "en_US.UTF-8" - parser = BuildOptions() - (options, args) = parser.parse_args() - ValidateOptions(options) - test_suite = TestSuite(options.tests, - options.strict_only, - options.non_strict_only, - options.unmarked_default, - options.with_test_expectations) - test_suite.Validate() - if options.cat: - test_suite.Print(args) - return 0 - else: - if test_suite.Run(options.command, args, - options.summary or options.full_summary, - options.full_summary, - options.parallel, - options.update_expectations): - return 0 - else: - return 1 - - -if __name__ == '__main__': - try: - sys.exit(Main()) - except Test262Error, e: - print "Error: %s" % e.message - sys.exit(1) diff --git a/tests/auto/qml/ecmascripttests/test262runner.cpp b/tests/auto/qml/ecmascripttests/test262runner.cpp index 90ac10a38b..d87a8a9552 100644 --- a/tests/auto/qml/ecmascripttests/test262runner.cpp +++ b/tests/auto/qml/ecmascripttests/test262runner.cpp @@ -222,10 +222,20 @@ void Test262Runner::createProcesses() } }); - QObject::connect(&p, &QProcess::finished, this, [&, i](int, QProcess::ExitStatus status) { - if (status != QProcess::NormalExit) { - qDebug() << QStringLiteral("Process %1 of %2 exited with a non-normal status") - .arg(i).arg(processCount - 1); + QObject::connect(&p, &QProcess::finished, this, + [this, processCount, i](int exitCode, QProcess::ExitStatus status) { + if (status != QProcess::NormalExit || exitCode != 0) { + TestData &testData(currentTasks[i]); + + auto &result = testData.stillNeedStrictRun + ? testData.sloppyResult + : testData.strictResult; + result = TestCase::Result( + TestCase::Crashes, + QStringLiteral("Process %1 of %2 exited with a non-normal status") + .arg(i).arg(processCount - 1)); + + addResult(testData); } --runningCount; diff --git a/tests/auto/qml/linebylinelex/BLACKLIST b/tests/auto/qml/linebylinelex/BLACKLIST deleted file mode 100644 index 0fd7f604e3..0000000000 --- a/tests/auto/qml/linebylinelex/BLACKLIST +++ /dev/null @@ -1,5 +0,0 @@ -# QTBUG-105697 -[testFormatter] -android -[testLineByLineLex] -android diff --git a/tests/auto/qml/linebylinelex/CMakeLists.txt b/tests/auto/qml/linebylinelex/CMakeLists.txt index 8b05ca0527..92d956a972 100644 --- a/tests/auto/qml/linebylinelex/CMakeLists.txt +++ b/tests/auto/qml/linebylinelex/CMakeLists.txt @@ -10,12 +10,12 @@ endif() # Collect linebyline test data file(GLOB_RECURSE test_data_glob - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. - linebylinelex/data/*) + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + data/*) # Collect qmlformat test data file(GLOB_RECURSE test_data_glob2 - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/.. - qmlformat/data/*) + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ../qmlformat/data/*) list(APPEND test_data ${test_data_glob} ${test_data_glob2}) qt_internal_add_test(tst_linebylinelex @@ -25,17 +25,5 @@ qt_internal_add_test(tst_linebylinelex Qt::Qml Qt::QuickTestUtilsPrivate TESTDATA ${test_data} -) - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_linebylinelex CONDITION ANDROID OR IOS - DEFINES - QT_QMLTEST_DATADIR=":/" -) - -qt_internal_extend_target(tst_linebylinelex CONDITION NOT ANDROID AND NOT IOS - DEFINES - QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/.." + BUILTIN_TESTDATA ) diff --git a/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp b/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp index d94f325020..040ccfa9a6 100644 --- a/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp +++ b/tests/auto/qml/linebylinelex/tst_linebylinelex.cpp @@ -13,7 +13,7 @@ QT_USE_NAMESPACE using namespace Qt::StringLiterals; using namespace QQmlJS; -class TestLineByLineLex : public QQmlDataTest +class TestLineByLineLex : public QObject { Q_OBJECT @@ -21,7 +21,7 @@ public: TestLineByLineLex(); private Q_SLOTS: - void initTestCase() override; + void initTestCase(); void testLineByLineLex_data(); void testLineByLineLex(); @@ -34,17 +34,14 @@ private: QString m_qmljsrootgenPath; QString m_qmltyperegistrarPath; - QString m_baseDir; }; TestLineByLineLex::TestLineByLineLex() - : QQmlDataTest(QT_QMLTEST_DATADIR), m_baseDir(QString::fromLocal8Bit(QT_QMLTEST_DATADIR)) { } void TestLineByLineLex::initTestCase() { - QQmlDataTest::initTestCase(); } void TestLineByLineLex::testLineByLineLex_data() @@ -59,22 +56,18 @@ void TestLineByLineLex::testLineByLineLex() { QFETCH(QString, filename); - QString filePath = m_baseDir + u"/linebylinelex/data/"_s + filename; + QString filePath = QFINDTESTDATA("data/" + filename); runLex(filePath); } void TestLineByLineLex::testFormatter_data() { QTest::addColumn<QString>("filename"); - QDir formatData(m_baseDir + u"/qmlformat/data"_s); - bool hasTestData = false; // ### TODO: fix test to always have data + QDir formatData(QFINDTESTDATA("qmlformat/data")); for (const QFileInfo &fInfo : formatData.entryInfoList(QStringList({ u"*.qml"_s, u"*.js"_s }), QDir::Files)) { QTest::newRow(qPrintable(fInfo.fileName())) << fInfo.absoluteFilePath(); - hasTestData = true; } - if (!hasTestData) - QSKIP("No test data found!"); } void TestLineByLineLex::testFormatter() diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index daa16eba72..7bf3b34f86 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -5948,7 +5948,7 @@ void tst_QJSEngine::functionCtorGeneratedCUIsNotCollectedByGc() const QString program = "new Function('a', 'b', 'let x = \"Hello\"; return a + b');"; auto sumFunc = engine.evaluate(program); QVERIFY(sumFunc.isCallable()); - auto *function = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&sumFunc); + auto *function = QJSValuePrivate::asManagedType<QV4::JavaScriptFunctionObject>(&sumFunc); auto *cu = function->d()->function->executableCompilationUnit(); QVERIFY(cu->runtimeStrings); // should exist for "Hello" QVERIFY(cu->runtimeStrings[0]->isMarked()); diff --git a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp index 76fa1328e7..450560833f 100644 --- a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp +++ b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp @@ -145,9 +145,11 @@ void tst_qjsonbinding::cppJsConversion() { QJSValue jsValue = eng.toScriptValue(jsonValue); +#if QT_DEPRECATED_SINCE(6, 9) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QVERIFY(!jsValue.isVariant()); QT_WARNING_POP +#endif switch (jsonValue.type()) { case QJsonValue::Null: QVERIFY(jsValue.isNull()); diff --git a/tests/auto/qml/qmlcachegen/CMakeLists.txt b/tests/auto/qml/qmlcachegen/CMakeLists.txt index 2dacff35b5..d88de460e9 100644 --- a/tests/auto/qml/qmlcachegen/CMakeLists.txt +++ b/tests/auto/qml/qmlcachegen/CMakeLists.txt @@ -28,6 +28,7 @@ qt_internal_add_test(tst_qmlcachegen Qt::Gui Qt::QmlPrivate Qt::QuickTestUtilsPrivate + Qt::QmlCompilerPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml new file mode 100644 index 0000000000..b48004dc87 --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsClean.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + property int i: 100 + property int j: i * 2 + + function s() : string { + let s = "abc" + return s + "def " + } +} diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml new file mode 100644 index 0000000000..3d96760e96 --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/AotstatsMixed.qml @@ -0,0 +1,7 @@ +import QtQml + +QtObject { + property int i: Math.max(1, 2) // OK + function f() { return 1 } // Error: No specified return type + property string s: g() // Error: g? +} diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc b/tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc new file mode 100644 index 0000000000..4f156ad2ef --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/cachegentest.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/cachegentest"> + <file alias="qmldir">qmldir</file> + </qresource> +</RCC> diff --git a/tests/auto/qml/qmlcachegen/data/aotstats/qmldir b/tests/auto/qml/qmlcachegen/data/aotstats/qmldir new file mode 100644 index 0000000000..72047cc99d --- /dev/null +++ b/tests/auto/qml/qmlcachegen/data/aotstats/qmldir @@ -0,0 +1,3 @@ +module cachegentest +AotstatsClean 254.0 AotstatsClean.qml +AotstatsMixed 254.0 AotstatsMixed.qml diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp index 17a914c1dd..34ad3bbc98 100644 --- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp +++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp @@ -3,6 +3,7 @@ #include <qtest.h> +#include <QJsonDocument> #include <QQmlComponent> #include <QQmlEngine> #include <QProcess> @@ -11,6 +12,7 @@ #include <QSysInfo> #include <QLoggingCategory> #include <private/qqmlcomponent_p.h> +#include <private/qqmljscompilerstats_p.h> #include <private/qqmlscriptdata_p.h> #include <private/qv4compileddata_p.h> #include <qtranslator.h> @@ -67,6 +69,10 @@ private slots: void scriptStringCachegenInteraction(); void saveableUnitPointer(); + + void aotstatsSerialization(); + void aotstatsGeneration_data(); + void aotstatsGeneration(); }; // A wrapper around QQmlComponent to ensure the temporary reference counts @@ -895,6 +901,133 @@ void tst_qmlcachegen::saveableUnitPointer() QCOMPARE(unit.flags, flags); } +void tst_qmlcachegen::aotstatsSerialization() +{ + const auto createEntry = [](const auto &d, const auto &n, const auto &e, const auto &l, + const auto &c, const auto &s) -> QQmlJS::AotStatsEntry { + QQmlJS::AotStatsEntry entry; + entry.codegenDuration = d; + entry.functionName = n; + entry.errorMessage = e; + entry.line = l; + entry.column = c; + entry.codegenSuccessful = s; + return entry; + }; + + const auto equal = [](const auto &e1, const auto &e2) -> bool { + return e1.codegenDuration == e2.codegenDuration && e1.functionName == e2.functionName + && e1.errorMessage == e2.errorMessage && e1.line == e2.line + && e1.column == e2.column && e1.codegenSuccessful == e2.codegenSuccessful; + }; + + // AotStats + // +-ModuleA + // | +-File1 + // | | +-e1 + // | | +-e2 + // | +-File2 + // | | +-e3 + // +-ModuleB + // | +-File3 + // | | +-e4 + + QQmlJS::AotStats original; + QQmlJS::AotStatsEntry e1 = createEntry(std::chrono::microseconds(500), "f1", "", 1, 1, true); + QQmlJS::AotStatsEntry e2 = createEntry(std::chrono::microseconds(200), "f2", "err1", 5, 4, false); + QQmlJS::AotStatsEntry e3 = createEntry(std::chrono::microseconds(750), "f3", "", 20, 4, true); + QQmlJS::AotStatsEntry e4 = createEntry(std::chrono::microseconds(300), "f4", "err2", 5, 8, false); + original.addEntry("ModuleA", "File1", e1); + original.addEntry("ModuleA", "File1", e2); + original.addEntry("ModuleA", "File2", e3); + original.addEntry("ModuleB", "File3", e4); + + const auto parsed = QQmlJS::AotStats::fromJsonDocument(original.toJsonDocument()); + QCOMPARE(parsed.entries().size(), original.entries().size()); + + const auto &parsedA = parsed.entries()["ModuleA"]; + const auto &originalA = original.entries()["ModuleA"]; + QCOMPARE(parsedA.size(), originalA.size()); + QCOMPARE(parsedA["File1"].size(), originalA["File1"].size()); + QVERIFY(equal(parsedA["File1"][0], originalA["File1"][0])); + QVERIFY(equal(parsedA["File1"][1], originalA["File1"][1])); + QCOMPARE(parsedA["File2"].size(), originalA["File2"].size()); + QVERIFY(equal(parsedA["File2"][0], originalA["File2"][0])); + + const auto &parsedB = parsed.entries()["ModuleB"]; + const auto &originalB = original.entries()["ModuleB"]; + QCOMPARE(parsedB.size(), originalB.size()); + QCOMPARE(parsedB["File3"].size(), originalB["File3"].size()); + QVERIFY(equal(parsedB["File3"][0], originalB["File3"][0])); +} + +struct FunctionEntry +{ + QString name; + QString errorMessage; + bool codegenSuccessful; +}; + +void tst_qmlcachegen::aotstatsGeneration_data() +{ + QTest::addColumn<QString>("qmlFile"); + QTest::addColumn<QList<FunctionEntry>>("entries"); + + QTest::addRow("clean") << "AotstatsClean.qml" + << QList<FunctionEntry>{ { "j", "", true }, { "s", "", true } }; + + const QString fError = "function without return type annotation returns int. This may prevent " + "proper compilation to Cpp."; + const QString sError = "method g cannot be resolved."; + QTest::addRow("mixed") << "AotstatsMixed.qml" + << QList<FunctionEntry>{ { "i", "", true }, + { "f", fError, false }, + { "s", sError, false } }; +} + +void tst_qmlcachegen::aotstatsGeneration() +{ +#if defined(QTEST_CROSS_COMPILED) + QSKIP("Cannot call qmlcachegen on cross-compiled target."); +#endif + QFETCH(QString, qmlFile); + QFETCH(QList<FunctionEntry>, entries); + + QTemporaryDir dir; + QProcess proc; + proc.setProgram(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + "/qmlcachegen"_L1); + const QString cppOutput = dir.filePath(qmlFile + ".cpp"); + const QString aotstatsOutput = cppOutput + ".aotstats"; + proc.setArguments({ "--bare", + "--resource-path", "/cachegentest/data/aotstats/" + qmlFile, + "-i", testFile("aotstats/qmldir"), + "--resource", testFile("aotstats/cachegentest.qrc"), + "--dump-aot-stats", + "--module-id=Aotstats", + "-o", cppOutput, + testFile("aotstats/" + qmlFile) }); + proc.start(); + QVERIFY(proc.waitForFinished() && proc.exitStatus() == QProcess::NormalExit); + + QVERIFY(QFileInfo::exists(aotstatsOutput)); + QFile aotstatsFile(aotstatsOutput); + QVERIFY(aotstatsFile.open(QIODevice::Text | QIODevice::ReadOnly)); + const auto document = QJsonDocument::fromJson(aotstatsFile.readAll()); + const auto aotstats = QQmlJS::AotStats::fromJsonDocument(document); + QVERIFY(aotstats.entries().size() == 1); // One module + const auto &moduleEntries = aotstats.entries()["Aotstats"]; + QVERIFY(moduleEntries.size() == 1); // Only one qml file was compiled + const auto &fileEntries = moduleEntries[moduleEntries.keys().first()]; + + for (const auto &entry : entries) { + const auto it = std::find_if(fileEntries.cbegin(), fileEntries.cend(), + [&](const auto &e) { return e.functionName == entry.name; }); + QVERIFY(it != fileEntries.cend()); + QVERIFY(it->codegenSuccessful == entry.codegenSuccessful); + QVERIFY(it->errorMessage == entry.errorMessage); + } +} + const QQmlScriptString &ScriptStringProps::undef() const { return m_undef; diff --git a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt index 42ad6d23d6..715ad6162a 100644 --- a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt @@ -19,6 +19,8 @@ qt_internal_add_test(tst_qmlcppcodegen codegen_test_moduleplugin codegen_test_hidden codegen_test_hiddenplugin + codegen_test_stringbuilder + codegen_test_stringbuilderplugin ) qt_internal_add_test(tst_qmlcppcodegen_interpreted @@ -31,6 +33,8 @@ qt_internal_add_test(tst_qmlcppcodegen_interpreted codegen_test_moduleplugin codegen_test_hidden codegen_test_hiddenplugin + codegen_test_stringbuilder + codegen_test_stringbuilderplugin DEFINES QT_TEST_FORCE_INTERPRETER ) diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 4d9b6aea41..7f2e6ad967 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -125,6 +125,7 @@ set(qml_files dialog.qml dialogButtonBox.qml dynamicscene.qml + enforceSignature.qml enumConversion.qml enumFromBadSingleton.qml enumInvalid.qml @@ -345,6 +346,33 @@ add_dependencies(codegen_test_hidden Qt::Quick) qt_autogen_tools_initial_setup(codegen_test_hiddenplugin) +qt_policy(SET QTP0004 NEW) + +qt_add_library(codegen_test_stringbuilder STATIC) +qt_autogen_tools_initial_setup(codegen_test_stringbuilder) + +set_target_properties(codegen_test_stringbuilder PROPERTIES + # We really want qmlcachegen here, even if qmlsc is available + QT_QMLCACHEGEN_EXECUTABLE qmlcachegen + QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks +) + +target_compile_definitions(codegen_test_stringbuilder PRIVATE + -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" + QT_USE_QSTRINGBUILDER +) + +qt6_add_qml_module(codegen_test_stringbuilder + URI StringBuilderTestTypes + SOURCES + writableVariantMap.h + QML_FILES + writeVariantMap.qml + OUTPUT_DIRECTORY stringbuilderTestTypes + __QT_INTERNAL_DISAMBIGUATE_QMLDIR_RESOURCE +) + +qt_autogen_tools_initial_setup(codegen_test_stringbuilderplugin) qt_add_library(codegen_test_module STATIC) qt_autogen_tools_initial_setup(codegen_test_module) @@ -355,7 +383,7 @@ set_target_properties(codegen_test_module PROPERTIES QT_QMLCACHEGEN_ARGUMENTS --validate-basic-blocks ) -qt_policy(SET QTP0004 NEW) + target_compile_definitions(codegen_test_module PUBLIC -DGENERATED_CPP_FOLDER="${CMAKE_CURRENT_BINARY_DIR}/.rcc/qmlcache" diff --git a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h index 2b6cc09c19..8dd640c67f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h +++ b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h @@ -52,7 +52,6 @@ private: }; struct Foozle { - Q_GADGET int foo = 1; }; diff --git a/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml b/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml new file mode 100644 index 0000000000..571a000199 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/enforceSignature.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + id: mainItem + + function arg(item: Binding) : QtObject { return item } + function ret(item: QtObject) : Binding { return item } + + property QtObject a: arg(mainItem); + property QtObject b: ret(mainItem); +} diff --git a/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h b/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h new file mode 100644 index 0000000000..3c0fedd28b --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writableVariantMap.h @@ -0,0 +1,31 @@ +#pragma once +#include <QObject> +#include <QVariantMap> +#include <QtQml/qqmlregistration.h> + +class WritableVariantMap : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QVariantMap data READ data WRITE setData NOTIFY dataChanged) + +public: + WritableVariantMap(QObject *parent = nullptr) : QObject(parent) { } + + QVariantMap data() const { return m_data; } + void setData(const QVariantMap &data) + { + if (m_data != data) { + m_data = data; + emit dataChanged(); + } + } + +signals: + void dataChanged(); + +private: + QVariantMap m_data; +}; + + diff --git a/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml b/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml new file mode 100644 index 0000000000..536e53b408 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/writeVariantMap.qml @@ -0,0 +1,10 @@ +pragma Strict +import StringBuilderTestTypes + +WritableVariantMap { + id: dragSource + property string modelData: "Drag Me" + data: ({ + "text/plain": "%" + dragSource.modelData + "%" + }) +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index ae8ef49b22..53cc068e8c 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -89,6 +89,7 @@ private slots: void enumProblems(); void enumScope(); void enums(); + void enforceSignature(); void enumsInOtherObject(); void equalityQObjects(); void equalityQUrl(); @@ -255,6 +256,7 @@ private slots: void voidConversion(); void voidFunction(); void writeBack(); + void writeVariantMap(); }; static QByteArray arg1() @@ -1652,6 +1654,23 @@ void tst_QmlCppCodegen::enums() } +void tst_QmlCppCodegen::enforceSignature() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/enforceSignature.qml"_s)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const QVariant a = object->property("a"); + QCOMPARE(a.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(a.value<QObject *>(), nullptr); + + const QVariant b = object->property("b"); + QCOMPARE(b.metaType(), QMetaType::fromType<QObject *>()); + QCOMPARE(b.value<QObject *>(), nullptr); +} + void tst_QmlCppCodegen::enumsInOtherObject() { QQmlEngine engine; @@ -1865,9 +1884,8 @@ void tst_QmlCppCodegen::failures() { const auto &aotFailure = QmlCacheGeneratedCode::_qt_qml_TestTypes_failures_qml::aotBuiltFunctions[0]; - QVERIFY(aotFailure.argumentTypes.isEmpty()); QVERIFY(!aotFailure.functionPtr); - QCOMPARE(aotFailure.extraData, 0); + QCOMPARE(aotFailure.functionIndex, 0); } void tst_QmlCppCodegen::fallbackLookups() @@ -5090,6 +5108,22 @@ void tst_QmlCppCodegen::writeBack() QCOMPARE(person->property("ints"), QVariant::fromValue(QList<int>({12, 22, 2, 1, 0, 0, 33}))); } +void tst_QmlCppCodegen::writeVariantMap() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/StringBuilderTestTypes/writeVariantMap.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const QVariantMap v = object->property("data").toMap(); + QCOMPARE(v.size(), 1); + const QVariant textPlain = v[u"text/plain"_s]; + QCOMPARE(textPlain.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(textPlain.toString(), u"%Drag Me%"_s); + +} + QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.moc" diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp index 0bf19bbe9f..da3ebc69a2 100644 --- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp +++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp @@ -509,9 +509,23 @@ void TestQmlformat::testExample_data() QString examples = QLatin1String(SRCDIR) + "/../../../../examples/"; QString tests = QLatin1String(SRCDIR) + "/../../../../tests/"; + QStringList exampleFiles; + QStringList testFiles; QStringList files; - files << findFiles(QDir(examples)); - files << findFiles(QDir(tests)); + exampleFiles << findFiles(QDir(examples)); + testFiles << findFiles(QDir(tests)); + + // Actually this test is an e2e test and not the unit test. + // At the moment of writing, CI lacks providing instruments for the automated tests + // which might be time-consuming, as for example this one. + // Therefore as part of QTBUG-122990 this test was copied to the /manual/e2e/qml/qmlformat + // however very small fraction of the test data is still preserved here for the sake of + // testing automatically at least a small part of the examples + const int nBatch = 10; + files << exampleFiles.mid(0, nBatch) << exampleFiles.mid(exampleFiles.size() / 2, nBatch) + << exampleFiles.mid(exampleFiles.size() - nBatch, nBatch); + files << testFiles.mid(0, nBatch) << testFiles.mid(exampleFiles.size() / 2, nBatch) + << testFiles.mid(exampleFiles.size() - nBatch, nBatch); for (const QString &file : files) QTest::newRow(qPrintable(file)) << file; diff --git a/tests/auto/qml/qmllint/data/Qtbug111015/qmldir b/tests/auto/qml/qmllint/data/Qtbug111015/qmldir new file mode 100644 index 0000000000..3bf1d48e13 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Qtbug111015/qmldir @@ -0,0 +1,3 @@ +module Qtbug111015 +typeinfo qtbug111015.qmltypes +import QtQml diff --git a/tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes b/tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes new file mode 100644 index 0000000000..7de521a379 --- /dev/null +++ b/tests/auto/qml/qmllint/data/Qtbug111015/qtbug111015.qmltypes @@ -0,0 +1,20 @@ +import QtQuick.tooling 1.2 + +Module { + Component { + file: "typewithjsonobjectlist.h" + name: "TypeWithJsonObjectList" + exports: ["QmlLintTestLib/TypeWithJsonObjectList 1.0"] + accessSemantics: "reference" + prototype: "QObject" + Property { name: "jsonObjectList"; type: "QJsonObject"; isList: true; read: "getJsonObjectList"; index: 0; isReadonly: true } + } + Component { + file: "typewithjsonarray.h" + name: "TypeWithJsonArray" + exports: ["QmlLintTestLib/TypeWithJsonArray 1.0"] + accessSemantics: "reference" + prototype: "QObject" + Property { name: "jsonArray"; type: "QJsonArray"; read: "getJsonArray"; index: 0; isReadonly: true } + } +} diff --git a/tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml b/tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml new file mode 100644 index 0000000000..89c52e0e52 --- /dev/null +++ b/tests/auto/qml/qmllint/data/jsonArrayIsRecognized.qml @@ -0,0 +1,8 @@ +import QtQuick +import Qtbug111015 1.0 + +Item { + TypeWithJsonArray { + jsonArray: [] + } +} diff --git a/tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml b/tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml new file mode 100644 index 0000000000..0a96fa9f92 --- /dev/null +++ b/tests/auto/qml/qmllint/data/jsonObjectIsRecognized.qml @@ -0,0 +1,8 @@ +import QtQuick +import Qtbug111015 1.0 + +Item { + TypeWithJsonObjectList { + jsonObjectList: [] + } +} diff --git a/tests/auto/qml/qmllint/data/returnTypeAnnotation_component.qml b/tests/auto/qml/qmllint/data/returnTypeAnnotation_component.qml new file mode 100644 index 0000000000..de142337b4 --- /dev/null +++ b/tests/auto/qml/qmllint/data/returnTypeAnnotation_component.qml @@ -0,0 +1,9 @@ +import QtQml +import QtQuick + +QtObject { + id: root + component Comp : Item { } + property Comp c: Comp{ } + function comp() { return root.c } +} diff --git a/tests/auto/qml/qmllint/data/returnTypeAnnotation_enum.qml b/tests/auto/qml/qmllint/data/returnTypeAnnotation_enum.qml new file mode 100644 index 0000000000..0585ceceb2 --- /dev/null +++ b/tests/auto/qml/qmllint/data/returnTypeAnnotation_enum.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + function enumeration() { return Text.AlignRight } +} diff --git a/tests/auto/qml/qmllint/data/returnTypeAnnotation_method.qml b/tests/auto/qml/qmllint/data/returnTypeAnnotation_method.qml new file mode 100644 index 0000000000..dc03311e73 --- /dev/null +++ b/tests/auto/qml/qmllint/data/returnTypeAnnotation_method.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + function f() { } + function method() { return f } +} diff --git a/tests/auto/qml/qmllint/data/returnTypeAnnotation_property.qml b/tests/auto/qml/qmllint/data/returnTypeAnnotation_property.qml new file mode 100644 index 0000000000..bb79978d85 --- /dev/null +++ b/tests/auto/qml/qmllint/data/returnTypeAnnotation_property.qml @@ -0,0 +1,7 @@ +import QtQml + +QtObject { + id: root + property int i: 1 + function prop() { return root.i } +} diff --git a/tests/auto/qml/qmllint/data/returnTypeAnnotation_type.qml b/tests/auto/qml/qmllint/data/returnTypeAnnotation_type.qml new file mode 100644 index 0000000000..78f02a8b67 --- /dev/null +++ b/tests/auto/qml/qmllint/data/returnTypeAnnotation_type.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + function type() { return 1 + 1 } +} diff --git a/tests/auto/qml/qmllint/data/something.qml b/tests/auto/qml/qmllint/data/something.qml new file mode 100644 index 0000000000..38998f606d --- /dev/null +++ b/tests/auto/qml/qmllint/data/something.qml @@ -0,0 +1,2 @@ +import ModuleInImportPath +A {} diff --git a/tests/auto/qml/qmllint/lintplugin.cpp b/tests/auto/qml/qmllint/lintplugin.cpp index 58b174cb6b..65795c103c 100644 --- a/tests/auto/qml/qmllint/lintplugin.cpp +++ b/tests/auto/qml/qmllint/lintplugin.cpp @@ -23,7 +23,7 @@ public: void run(const QQmlSA::Element &element) override { auto property = element.property(u"radius"_s); - if (!property.isValid() || element.property(u"radius"_s).typeName() != u"double") { + if (!property.isValid() || element.property(u"radius"_s).typeName() != u"qreal") { emitWarning(u"Failed to verify radius property", plugin, element.sourceLocation()); return; } diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 4e69fc2e9e..6bd4d6c90a 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -105,6 +105,7 @@ private Q_SLOTS: void valueTypesFromString(); void ignoreSettingsNotCommandLineOptions(); + void backslashedQmldirPath(); void environment_data(); void environment(); @@ -113,6 +114,7 @@ private Q_SLOTS: void testPlugin(); void quickPlugin(); #endif + private: enum DefaultImportOption { NoDefaultImports, UseDefaultImports }; enum ContainOption { StringNotContained, StringContained }; @@ -1026,7 +1028,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", { Message { QStringLiteral("Ready") } } } }; QTest::newRow("nullBinding") << QStringLiteral("nullBinding.qml") << Result{ { Message{ QStringLiteral( - "Cannot assign literal of type null to double") } } }; + "Cannot assign literal of type null to qreal") } } }; QTest::newRow("missingRequiredAlias") << QStringLiteral("missingRequiredAlias.qml") << Result { { Message { @@ -1345,6 +1347,8 @@ void TestQmllint::cleanQmlCode_data() QTest::newRow("locale") << QStringLiteral("locale.qml"); QTest::newRow("constInvokable") << QStringLiteral("useConstInvokable.qml"); QTest::newRow("dontCheckJSTypes") << QStringLiteral("dontCheckJSTypes.qml"); + QTest::newRow("jsonObjectIsRecognized") << QStringLiteral("jsonObjectIsRecognized.qml"); + QTest::newRow("jsonArrayIsRecognized") << QStringLiteral("jsonArrayIsRecognized.qml"); } void TestQmllint::cleanQmlCode() @@ -1368,10 +1372,11 @@ void TestQmllint::compilerWarnings_data() QTest::newRow("qQmlV4Function") << QStringLiteral("varargs.qml") << Result::clean() << true; QTest::newRow("multiGrouped") << QStringLiteral("multiGrouped.qml") << Result::clean() << true; - QTest::newRow("shadowable") << QStringLiteral("shadowable.qml") - << Result { { Message { QStringLiteral( - "with type NotSoSimple can be shadowed") } } } - << true; + QTest::newRow("shadowable") + << QStringLiteral("shadowable.qml") + << Result { { Message {QStringLiteral( + "with type NotSoSimple (stored as QQuickItem) can be shadowed") } } } + << true; QTest::newRow("tooFewParameters") << QStringLiteral("tooFewParams.qml") << Result { { Message { QStringLiteral("No matching override found") } } } << true; @@ -1402,6 +1407,39 @@ void TestQmllint::compilerWarnings_data() QStringLiteral("Cannot retrieve a non-object type by ID: stateMachine") } } } << true; + QTest::newRow("returnTypeAnnotation-component") + << QStringLiteral("returnTypeAnnotation_component.qml") + << Result{ { { "Could not compile function comp: function without return type " + "annotation returns (component in" }, + { "returnTypeAnnotation_component.qml)::c with type Comp (stored as " + "QQuickItem). This may prevent proper compilation to Cpp." } } } + << true; + QTest::newRow("returnTypeAnnotation-enum") + << QStringLiteral("returnTypeAnnotation_enum.qml") + << Result{ { { "Could not compile function enumeration: function without return type " + "annotation returns QQuickText::HAlignment::AlignRight (stored as int). " + "This may prevent proper compilation to Cpp." } } } + << true; + QTest::newRow("returnTypeAnnotation-method") + << QStringLiteral("returnTypeAnnotation_method.qml") + << Result{ { { "Could not compile function method: function without return type " + "annotation returns (component in " }, // Don't check the build folder path + { "returnTypeAnnotation_method.qml)::f(...) (stored as QJSValue). This may " + "prevent proper compilation to Cpp." } } } + << true; + QTest::newRow("returnTypeAnnotation-property") + << QStringLiteral("returnTypeAnnotation_property.qml") + << Result{ { { "Could not compile function prop: function without return type " + "annotation returns (component in " }, // Don't check the build folder path + { "returnTypeAnnotation_property.qml)::i with type int. This may prevent " + "proper compilation to Cpp." } } } + << true; + QTest::newRow("returnTypeAnnotation-type") + << QStringLiteral("returnTypeAnnotation_type.qml") + << Result{ { { "Could not compile function type: function without return type " + "annotation returns double. This may prevent proper compilation to " + "Cpp." } } } + << true; } void TestQmllint::compilerWarnings() @@ -2086,7 +2124,7 @@ void TestQmllint::quickPlugin() Message { u"SplitView attached property only works with Items"_s }, Message { u"ScrollIndicator must be attached to a Flickable"_s }, Message { u"ScrollBar must be attached to a Flickable or ScrollView"_s }, - Message { u"Accessible must be attached to an Item"_s }, + Message { u"Accessible must be attached to an Item or an Action"_s }, Message { u"EnterKey attached property only works with Items"_s }, Message { u"LayoutDirection attached property only works with Items and Windows"_s }, @@ -2218,5 +2256,14 @@ void TestQmllint::ignoreSettingsNotCommandLineOptions() QCOMPARE(output, QString()); } +void TestQmllint::backslashedQmldirPath() +{ + const QString qmldirPath + = testFile(u"ImportPath/ModuleInImportPath/qmldir"_s).replace('/', QDir::separator()); + const QString output = runQmllint( + testFile(u"something.qml"_s), true, QStringList{ u"-i"_s, qmldirPath }); + QVERIFY(output.isEmpty()); +} + QTEST_GUILESS_MAIN(TestQmllint) #include "tst_qmllint.moc" diff --git a/tests/auto/qml/qmltc_qprocess/CMakeLists.txt b/tests/auto/qml/qmltc_qprocess/CMakeLists.txt index 55266c80d9..85c01d0a5f 100644 --- a/tests/auto/qml/qmltc_qprocess/CMakeLists.txt +++ b/tests/auto/qml/qmltc_qprocess/CMakeLists.txt @@ -27,6 +27,7 @@ qt6_add_qml_module(tst_qmltc_qprocess URI QmltcQProcessTests SOURCES cpptypes/testtype.h + cpptypes/typewithrequiredproperty.h DEPENDENCIES QtQuick/auto QML_FILES @@ -43,6 +44,10 @@ qt6_add_qml_module(tst_qmltc_qprocess data/QmlBaseFromAnotherModule.qml data/invalidTypeAnnotation.qml data/constructFromString.qml + data/unboundRequiredPropertyInInlineComponent.qml + data/componentDefinitionInnerRequiredProperty.qml + data/componentDefinitionInnerRequiredPropertyFromOutside.qml + data/innerLevelRequiredProperty.qml ) set(common_libraries diff --git a/tests/auto/qml/qmltc_qprocess/cpptypes/typewithrequiredproperty.h b/tests/auto/qml/qmltc_qprocess/cpptypes/typewithrequiredproperty.h new file mode 100644 index 0000000000..1b0c69efaa --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/cpptypes/typewithrequiredproperty.h @@ -0,0 +1,27 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TYPEWITHREQUIREDPROPERTY_H_ +#define TYPEWITHREQUIREDPROPERTY_H_ + +#include <QtCore/qobject.h> +#include <QtCore/qproperty.h> +#include <QtQml/qqmlregistration.h> + +class TypeWithRequiredProperty : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QString requiredProperty READ requiredProperty WRITE setRequiredProperty REQUIRED) + + QProperty<QString> m_requiredProperty; + +public: + TypeWithRequiredProperty(QObject *parent = nullptr) : QObject(parent) { } + + QString requiredProperty() const { return m_requiredProperty; } + void setRequiredProperty(const QString &s) { m_requiredProperty = s; } +}; + +#endif // TYPEWITHREQUIREDPROPERTY_H_ diff --git a/tests/auto/qml/qmltc_qprocess/data/componentDefinitionInnerRequiredProperty.qml b/tests/auto/qml/qmltc_qprocess/data/componentDefinitionInnerRequiredProperty.qml new file mode 100644 index 0000000000..638cf9fa1e --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/data/componentDefinitionInnerRequiredProperty.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + Component { + id: mycomp + + Item { + Rectangle { + // Inner required properties cannot be set so this + // should produce an error + required property bool bar + } + } + } +} diff --git a/tests/auto/qml/qmltc_qprocess/data/componentDefinitionInnerRequiredPropertyFromOutside.qml b/tests/auto/qml/qmltc_qprocess/data/componentDefinitionInnerRequiredPropertyFromOutside.qml new file mode 100644 index 0000000000..56f00edbe9 --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/data/componentDefinitionInnerRequiredPropertyFromOutside.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +import QmltcQProcessTests + +Item { + Component { + id: mycomp + + Item { + // This introduces an inner required property + // without a binding that cannot be set later and should + // thus block the compilation. + TypeWithRequiredProperty {} + } + } +} diff --git a/tests/auto/qml/qmltc_qprocess/data/innerLevelRequiredProperty.qml b/tests/auto/qml/qmltc_qprocess/data/innerLevelRequiredProperty.qml new file mode 100644 index 0000000000..8b28418670 --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/data/innerLevelRequiredProperty.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + Item { + required property int foo + } +} diff --git a/tests/auto/qml/qmltc_qprocess/data/unboundRequiredPropertyInInlineComponent.qml b/tests/auto/qml/qmltc_qprocess/data/unboundRequiredPropertyInInlineComponent.qml new file mode 100644 index 0000000000..3955a228d8 --- /dev/null +++ b/tests/auto/qml/qmltc_qprocess/data/unboundRequiredPropertyInInlineComponent.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Item { + component InlineComponent: Item { required property int foo } + + InlineComponent {} +} diff --git a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp index fdb64f4a29..31c27c3cd7 100644 --- a/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp +++ b/tests/auto/qml/qmltc_qprocess/tst_qmltc_qprocess.cpp @@ -55,6 +55,10 @@ private slots: void qmlBaseFromAnotherModule(); void invalidTypeAnnotation(); void constructFromString(); + void unboundRequiredPropertyInInlineComponent(); + void componentDefinitionInnerRequiredProperty(); + void componentDefinitionInnerRequiredPropertyFromOutside(); + void innerLevelRequiredProperty(); }; #ifndef TST_QMLTC_QPROCESS_RESOURCES @@ -340,5 +344,46 @@ void tst_qmltc_qprocess::constructFromString() QVERIFY(errors.contains(warningMessage.arg(6).arg(23).arg(u"QSizeF"))); } +void tst_qmltc_qprocess::unboundRequiredPropertyInInlineComponent() +{ + { + const auto errors = runQmltc(u"unboundRequiredPropertyInInlineComponent.qml"_s, false); + QVERIFY(errors.contains( + u"unboundRequiredPropertyInInlineComponent.qml:9:5: Component is missing required property foo from InlineComponent [required]"_s + )); + } +} + +void tst_qmltc_qprocess::componentDefinitionInnerRequiredProperty() +{ + { + const auto errors = runQmltc(u"componentDefinitionInnerRequiredProperty.qml"_s, false); + QVERIFY(errors.contains( + u"componentDefinitionInnerRequiredProperty.qml:11:13: Component is missing required property bar from here [required]" + )); + } +} + +void tst_qmltc_qprocess::componentDefinitionInnerRequiredPropertyFromOutside() +{ + { + const auto errors = + runQmltc(u"componentDefinitionInnerRequiredPropertyFromOutside.qml"_s, false); + QVERIFY(errors.contains( + u"componentDefinitionInnerRequiredPropertyFromOutside.qml:15:13: Component is missing required property requiredProperty from TypeWithRequiredProperty [required]" + )); + } +} + +void tst_qmltc_qprocess::innerLevelRequiredProperty() +{ + { + const auto errors = runQmltc(u"innerLevelRequiredProperty.qml"_s, false); + QVERIFY(errors.contains( + u"innerLevelRequiredProperty.qml:7:5: Component is missing required property foo from here [required]" + )); + } +} + QTEST_MAIN(tst_qmltc_qprocess) #include "tst_qmltc_qprocess.moc" diff --git a/tests/auto/qml/qmltyperegistrar/foreign/CMakeLists.txt b/tests/auto/qml/qmltyperegistrar/foreign/CMakeLists.txt index 5334225692..68223ae6a5 100644 --- a/tests/auto/qml/qmltyperegistrar/foreign/CMakeLists.txt +++ b/tests/auto/qml/qmltyperegistrar/foreign/CMakeLists.txt @@ -10,7 +10,8 @@ qt_internal_add_cmake_library(foreign STATIC SOURCES - foreign.cpp foreign.h foreign_p.h + foreign.cpp foreign.h + private/foreign_p.h PUBLIC_LIBRARIES Qt::Core ) diff --git a/tests/auto/qml/qmltyperegistrar/foreign/foreign_p.h b/tests/auto/qml/qmltyperegistrar/foreign/private/foreign_p.h index 6245dad796..ed23d78284 100644 --- a/tests/auto/qml/qmltyperegistrar/foreign/foreign_p.h +++ b/tests/auto/qml/qmltyperegistrar/foreign/private/foreign_p.h @@ -7,7 +7,6 @@ #include <QtCore/qobject.h> // qmltyperegistrar will assume this file is reachable under <private/foreign_p.h> -// It's not true, but this is how it works on actual private headers in Qt. // See the trick in tst_qmltyperegistrar's CMakeLists.txt to turn on the --private-includes option. class ForeignPrivate : public QObject { diff --git a/tests/auto/qml/qmltyperegistrar/missingTypes.json b/tests/auto/qml/qmltyperegistrar/missingTypes.json index 3b57ae3c55..dacec11c4c 100644 --- a/tests/auto/qml/qmltyperegistrar/missingTypes.json +++ b/tests/auto/qml/qmltyperegistrar/missingTypes.json @@ -5,6 +5,25 @@ "classInfos": [ { "name": "QML.Element", + "value": "int" + }, + { + "name": "QML.Extended", + "value": "QQmlIntForeign" + }, + { + "name": "QML.Foreign", + "value": "int" + } + ], + "className": "QQmlIntForeign", + "gadget": true, + "qualifiedClassName": "QQmlIntForeign" + }, + { + "classInfos": [ + { + "name": "QML.Element", "value": "auto" } ], @@ -28,6 +47,20 @@ "write": "setPalette" } ], + "enums": [ + { + "isClass": false, + "isFlag": false, + "name": "RestorationMode", + "type": "NotAnUnderlyingType", + "values": [ + "RestoreNone", + "RestoreBinding", + "RestoreValue", + "RestoreBindingOrValue" + ] + } + ], "qualifiedClassName": "ExcessiveVersion", "signals": [ { @@ -69,22 +102,20 @@ "revision": 260, "scriptable": true, "stored": true, - "type": "int", + "type": "NotAPropertyType", "user": false - }, + } + ], + "methods": [ { - "constant": true, - "designable": true, - "final": false, - "index": 1, - "name": "insane", - "read": "revisioned", - "required": false, - "revision": 65297, - "scriptable": true, - "stored": true, - "type": "int", - "user": false + "access": "public", + "arguments": [ + { + "type": "NotAnArgumentType" + } + ], + "name": "createAThing", + "returnType": "NotAReturnType" } ], "qualifiedClassName": "AddedInLateVersion", diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp index 563c88f850..7ea5dcb51c 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.cpp @@ -441,11 +441,22 @@ void tst_qmltyperegistrar::consistencyWarnings() QTest::ignoreMessage(QtWarningMsg, message); }; - expectWarning("Warning: tst_qmltyperegistrar.h:: NotQObject is used but cannot be found."); - expectWarning("Warning: tst_qmltyperegistrar.h:: NotQObject is used but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: " + "NotQObject is used as base type but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: NotQObject is used as base type " + "but cannot be found."); expectWarning("Warning: tst_qmltyperegistrar.h:: Invisible is declared as foreign type, " "but cannot be found."); - expectWarning("Warning: tst_qmltyperegistrar.h:: NotQByteArray is used but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: NotQByteArray is used as sequence value type " + "but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: NotAPropertyType is used as property type " + "but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: NotAnArgumentType is used as argument type " + "but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: NotAReturnType is used as return type " + "but cannot be found."); + expectWarning("Warning: tst_qmltyperegistrar.h:: NotAnUnderlyingType is used as enum type " + "but cannot be found."); processor.postProcessForeignTypes(); @@ -728,47 +739,47 @@ void tst_qmltyperegistrar::typedEnum() exportMetaObjectRevisions: [256] Enum { name: "UChar" - type: "quint8" + type: "uchar" values: ["V0"] } Enum { name: "Int8_T" - type: "qint8" + type: "int8_t" values: ["V1"] } Enum { name: "UInt8_T" - type: "quint8" + type: "uint8_t" values: ["V2"] } Enum { name: "Int16_T" - type: "short" + type: "int16_t" values: ["V3"] } Enum { name: "UInt16_T" - type: "ushort" + type: "uint16_t" values: ["V4"] } Enum { name: "Int32_T" - type: "int" + type: "int32_t" values: ["V5"] } Enum { name: "UInt32_T" - type: "uint" + type: "uint32_t" values: ["V6"] } Enum { name: "S" - type: "short" + type: "qint16" values: ["A", "B", "C"] } Enum { name: "T" - type: "ushort" + type: "quint16" values: ["D", "E", "F"] } Enum { @@ -794,7 +805,7 @@ void tst_qmltyperegistrar::listSignal() prototype: "QObject" Signal { name: "objectListHappened" - Parameter { type: "QObjectList" } + Parameter { type: "QList<QObject*>" } } })")); } @@ -982,10 +993,10 @@ void tst_qmltyperegistrar::longNumberTypes() exports: ["QmlTypeRegistrarTest/LongNumberTypes 1.0"] isCreatable: true exportMetaObjectRevisions: [256] - Property { name: "a"; type: "qlonglong"; index: 0 } - Property { name: "b"; type: "qlonglong"; index: 1 } - Property { name: "c"; type: "qulonglong"; index: 2 } - Property { name: "d"; type: "qulonglong"; index: 3 } + Property { name: "a"; type: "qint64"; index: 0 } + Property { name: "b"; type: "int64_t"; index: 1 } + Property { name: "c"; type: "quint64"; index: 2 } + Property { name: "d"; type: "uint64_t"; index: 3 } })")); } @@ -1012,4 +1023,18 @@ void tst_qmltyperegistrar::constReturnType() })")); } +void tst_qmltyperegistrar::usingDeclaration() +{ + QVERIFY(qmltypesData.contains(R"(Component { + file: "tst_qmltyperegistrar.h" + name: "WithMyInt" + accessSemantics: "reference" + prototype: "QObject" + exports: ["QmlTypeRegistrarTest/WithMyInt 1.0"] + isCreatable: true + exportMetaObjectRevisions: [256] + Property { name: "a"; type: "int"; read: "a"; index: 0; isReadonly: true; isConstant: true } + })")); +} + QTEST_MAIN(tst_qmltyperegistrar) diff --git a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h index 5ebb9b6796..efb40fd426 100644 --- a/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h +++ b/tests/auto/qml/qmltyperegistrar/tst_qmltyperegistrar.h @@ -5,7 +5,7 @@ #define TST_QMLTYPEREGISTRAR_H #include "foreign.h" -#include "foreign_p.h" +#include "private/foreign_p.h" #include <QtQmlTypeRegistrar/private/qqmltyperegistrar_p.h> @@ -792,6 +792,24 @@ public: Q_INVOKABLE const QObject *getObject() { return nullptr; } }; +using myint = int; + +struct IntAlias +{ + Q_GADGET + QML_FOREIGN(myint) + QML_USING(int); +}; + +class WithMyInt : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(myint a READ a CONSTANT) +public: + myint a() const { return 10; } +}; + class tst_qmltyperegistrar : public QObject { Q_OBJECT @@ -865,6 +883,8 @@ private slots: void enumList(); void constReturnType(); + void usingDeclaration(); + private: QByteArray qmltypesData; }; diff --git a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp index 494d765798..b13379a103 100644 --- a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp +++ b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp @@ -1,13 +1,17 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <qtest.h> + +#include "WithBindableProperties.h" + +#include <private/qmlutils_p.h> +#include <private/qqmlbind_p.h> +#include <private/qqmlcomponentattached_p.h> +#include <private/qquickrectangle_p.h> + +#include <QtTest/qtest.h> + #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> -#include <QtQml/private/qqmlbind_p.h> -#include <QtQml/private/qqmlcomponentattached_p.h> -#include <QtQuick/private/qquickrectangle_p.h> -#include <QtQuickTestUtils/private/qmlutils_p.h> -#include "WithBindableProperties.h" class tst_qqmlbinding : public QQmlDataTest { diff --git a/tests/auto/qml/qqmlengine/CMakeLists.txt b/tests/auto/qml/qqmlengine/CMakeLists.txt index 7951baedb8..9745f31bdb 100644 --- a/tests/auto/qml/qqmlengine/CMakeLists.txt +++ b/tests/auto/qml/qqmlengine/CMakeLists.txt @@ -40,6 +40,7 @@ qt_add_qml_module(tst_qqmlengine_qml SOURCES "declarativelyregistered.h" "declarativelyregistered.cpp" + "variantlistQJsonConversion.h" RESOURCE_PREFIX "/" OUTPUT_DIRECTORY diff --git a/tests/auto/qml/qqmlengine/data/variantListQJsonConversion.qml b/tests/auto/qml/qqmlengine/data/variantListQJsonConversion.qml new file mode 100644 index 0000000000..fd0820a3c5 --- /dev/null +++ b/tests/auto/qml/qqmlengine/data/variantListQJsonConversion.qml @@ -0,0 +1,18 @@ +import QtQuick +import OnlyDeclarative + +Item { + + MiscUtils { + id: miscUtils + } + + Component.onCompleted: { + const varlist = miscUtils.createVariantList(); + const obj = { test: varlist }; + const listProperty = miscUtils.createQmlListProperty(); + miscUtils.logArray(varlist); + miscUtils.logObject(obj); + miscUtils.logArray(listProperty); + } +} diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index ec0d1b4386..3c25d29dfb 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -36,8 +36,10 @@ public: private slots: void initTestCase() override; void rootContext(); +#if QT_CONFIG(qml_network) void networkAccessManager(); void synchronousNetworkAccessManager(); +#endif void baseUrl(); void contextForObject(); void offlineStoragePath(); @@ -80,6 +82,7 @@ private slots: void lockedRootObject(); void crossReferencingSingletonsDeletion(); void bindingInstallUseAfterFree(); + void variantListQJsonConversion(); public slots: QObject *createAQObjectForOwnershipTest () @@ -152,6 +155,7 @@ void tst_qqmlengine::rootContext() QVERIFY(!engine.rootContext()->parentContext()); } +#if QT_CONFIG(qml_network) class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory { public: @@ -227,7 +231,7 @@ void tst_qqmlengine::synchronousNetworkAccessManager() // reply is finished, so should not be in loading state. QVERIFY(!c.isLoading()); } - +#endif void tst_qqmlengine::baseUrl() { @@ -1734,6 +1738,21 @@ void tst_qqmlengine::bindingInstallUseAfterFree() QVERIFY(o); } +void tst_qqmlengine::variantListQJsonConversion() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("variantListQJsonConversion.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage(QtMsgType::QtDebugMsg, R"(["cpp","variant","list"])"); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, R"({"test":["cpp","variant","list"]})"); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, + R"([{"objectName":"o0"},{"objectName":"o1"},{"objectName":"o2"}])"); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); +} + QTEST_MAIN(tst_qqmlengine) #include "tst_qqmlengine.moc" diff --git a/tests/auto/qml/qqmlengine/variantlistQJsonConversion.h b/tests/auto/qml/qqmlengine/variantlistQJsonConversion.h new file mode 100644 index 0000000000..edf2174a18 --- /dev/null +++ b/tests/auto/qml/qqmlengine/variantlistQJsonConversion.h @@ -0,0 +1,53 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef VARIANTLIST_QJSON_CONVERSION_HPP +#define VARIANTLIST_QJSON_CONVERSION_HPP + +#include "qqmlintegration.h" +#include <QJsonObject> +#include <QJsonArray> +#include <QObject> +#include <QJsonDocument> +#include <QDebug> +#include <private/qjsvalue_p.h> +#include <private/qqmllistwrapper_p.h> +#include <private/qv4engine_p.h> +#include <private/qv4jsonobject_p.h> + +class MiscUtils : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + Q_INVOKABLE QVariantList createVariantList() const + { + return { QString("cpp"), QString("variant"), QString("list") }; + } + + Q_INVOKABLE QQmlListProperty<QObject> createQmlListProperty() + { + QV4::ExecutionEngine engine(qmlEngine(this)); + static QObject objects[] = { QObject{}, QObject{}, QObject{} }; + objects[0].setObjectName("o0"); + objects[1].setObjectName("o1"); + objects[2].setObjectName("o2"); + static QList<QObject *> list{ &objects[0], &objects[1], &objects[2] }; + return QQmlListProperty<QObject>(this, &list); + } + + Q_INVOKABLE void logArray(const QJsonArray &arr) const + { + const auto str = QString(QJsonDocument(arr).toJson(QJsonDocument::Compact)); + qDebug().noquote() << str; + } + + Q_INVOKABLE void logObject(const QJsonObject &obj) const + { + const auto str = QString(QJsonDocument(obj).toJson(QJsonDocument::Compact)); + qDebug().noquote() << str; + } +}; + +#endif // VARIANTLIST_QJSON_CONVERSION_HPP diff --git a/tests/auto/qml/qqmlimport/CMakeLists.txt b/tests/auto/qml/qqmlimport/CMakeLists.txt index 803234787b..b8b720f5dc 100644 --- a/tests/auto/qml/qqmlimport/CMakeLists.txt +++ b/tests/auto/qml/qqmlimport/CMakeLists.txt @@ -35,6 +35,7 @@ qt_internal_add_test(tst_qqmlimport SOURCES tst_qqmlimport.cpp LIBRARIES + Qt::CorePrivate Qt::Gui Qt::Qml Qt::QmlPrivate @@ -58,6 +59,13 @@ qt_internal_add_resource(tst_qqmlimport "preferred2" "qmldir" ) +qt_internal_add_resource(tst_qqmlimport "qtconf" + PREFIX + "/" + FILES + "qmlimports.qt.conf" +) + ## Scopes: ##################################################################### diff --git a/tests/auto/qml/qqmlimport/qmlimports.qt.conf b/tests/auto/qml/qqmlimport/qmlimports.qt.conf new file mode 100644 index 0000000000..3a63cc797f --- /dev/null +++ b/tests/auto/qml/qqmlimport/qmlimports.qt.conf @@ -0,0 +1,3 @@ +[Paths] +Prefix = "" +QmlImports = ":/a/path", ":/another/path", ":/even/more/path" diff --git a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp index ff1513d0d6..fe14281387 100644 --- a/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp +++ b/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp @@ -17,6 +17,7 @@ #include <QtCore/qscopeguard.h> #include <QtCore/qlibraryinfo.h> +#include <QtCore/private/qlibraryinfo_p.h> class TheThing : public QObject { @@ -68,6 +69,7 @@ private slots: void qualifiedScriptImport(); void invalidImportUrl(); void registerTypesFromImplicitImport(); + void containsAllQtConfEntries(); private: QQmlModuleRegistration noimportRegistration; @@ -202,6 +204,22 @@ void tst_QQmlImport::registerTypesFromImplicitImport() QCOMPARE(t->m_width, 640); } +void tst_QQmlImport::containsAllQtConfEntries() +{ + QString qtConfPath(u":/qmlimports.qt.conf"); + QLibraryInfoPrivate::setQtconfManualPath(&qtConfPath); + QLibraryInfoPrivate::reload(); + auto cleanup = qScopeGuard([](){ + QLibraryInfoPrivate::setQtconfManualPath(nullptr); + QLibraryInfoPrivate::reload(); + }); + QQmlEngine engine; + auto importPaths = engine.importPathList(); + QVERIFY(importPaths.contains(u"qrc:/a/path")); + QVERIFY(importPaths.contains(u"qrc:/another/path")); + QVERIFY(importPaths.contains(u"qrc:/even/more/path")); +} + void tst_QQmlImport::testDesignerSupported() { std::unique_ptr<QQuickView> window = std::make_unique<QQuickView>(); diff --git a/tests/auto/qml/qqmljsscope/data/ownModuleName.qml b/tests/auto/qml/qqmljsscope/data/ownModuleName.qml new file mode 100644 index 0000000000..6e43ce6b05 --- /dev/null +++ b/tests/auto/qml/qqmljsscope/data/ownModuleName.qml @@ -0,0 +1,10 @@ +import QtQuick + +Item { + Item { id: child } + component IC: Item { + Item { + id: childInIC + } + } +} diff --git a/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp b/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp index db81c77206..4dacc17f94 100644 --- a/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp +++ b/tests/auto/qml/qqmljsscope/tst_qqmljsscope.cpp @@ -71,6 +71,7 @@ class tst_qqmljsscope : public QQmlDataTest logger.setCode(sourceCode); logger.setSilent(expectErrorsOrWarnings); QQmlJSScope::Ptr target = QQmlJSScope::create(); + target->setOwnModuleName(u"HelloModule"_s); QQmlJSImportVisitor visitor(target, &m_importer, &logger, dataDirectory()); QQmlJSTypeResolver typeResolver { &m_importer }; typeResolver.init(&visitor, document->program); @@ -108,6 +109,7 @@ private Q_SLOTS: void extensions(); void emptyBlockBinding(); void hasOwnEnumerationKeys(); + void ownModuleName(); void resolvedNonUniqueScopes(); void compilationUnitsAreCompatible(); void attachedTypeResolution_data(); @@ -356,7 +358,7 @@ void tst_qqmljsscope::descriptiveNameOfNull() stored, property, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ScopeProperty, QQmlJSScope::ConstPtr()); - QCOMPARE(unscoped.descriptiveName(), u"bar of (invalid type)::foo with type baz"_s); + QCOMPARE(unscoped.descriptiveName(), u"(invalid type)::foo with type baz (stored as bar)"_s); } void tst_qqmljsscope::groupedPropertiesConsistency() @@ -709,6 +711,33 @@ void tst_qqmljsscope::hasOwnEnumerationKeys() QVERIFY(!extended->hasOwnEnumerationKey(u"ThisIsTheFlagFromExtension"_s)); } +void tst_qqmljsscope::ownModuleName() +{ + const QString moduleName = u"HelloModule"_s; + QQmlJSScope::ConstPtr root = run(u"ownModuleName.qml"_s); + QVERIFY(root); + QCOMPARE(root->moduleName(), moduleName); + QCOMPARE(root->ownModuleName(), moduleName); + + QCOMPARE(root->childScopes().size(), 2); + QQmlJSScope::ConstPtr child = root->childScopes().front(); + QVERIFY(child); + // only root and inline components have own module names, but the child should be able to query + // its component's module Name via moduleName() + QCOMPARE(child->ownModuleName(), QString()); + QCOMPARE(child->moduleName(), moduleName); + + QQmlJSScope::ConstPtr ic = root->childScopes()[1]; + QVERIFY(ic); + QCOMPARE(ic->ownModuleName(), moduleName); + QCOMPARE(ic->moduleName(), moduleName); + + QQmlJSScope::ConstPtr icChild = ic->childScopes().front(); + QVERIFY(icChild); + QCOMPARE(icChild->ownModuleName(), QString()); + QCOMPARE(icChild->moduleName(), moduleName); +} + void tst_qqmljsscope::resolvedNonUniqueScopes() { QQmlJSScope::ConstPtr root = run(u"resolvedNonUniqueScope.qml"_s); diff --git a/tests/auto/qml/qqmllanguage/data/asValueType.qml b/tests/auto/qml/qqmllanguage/data/asValueType.qml index 6a5500e344..b51dc9c6ec 100644 --- a/tests/auto/qml/qqmllanguage/data/asValueType.qml +++ b/tests/auto/qml/qqmllanguage/data/asValueType.qml @@ -10,4 +10,11 @@ QtObject { property var e: ({x: 10, y: 20}) as point property var f: "red" as withString property var g: "green" as string + property rect bb + property var p: bb as size; + property var q: this as size; + property var r: ({}) as size; + property var s: 11 as size; + property var t: Component as size; + property var u: Qt as size; } diff --git a/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml b/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml new file mode 100644 index 0000000000..777ada3848 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml @@ -0,0 +1,35 @@ +pragma ValueTypeBehavior: Assertable +import QtQml as Q +import StaticTest as S + +Q.QtObject { + property var a + property rect b: a as Q.rect + property bool c: a instanceof Q.rect + property bool d: ({x: 10, y: 20}) instanceof Q.point + property var e: ({x: 10, y: 20}) as Q.point + property var f: "red" as S.withString + property var g: "green" as Q.string + + property var h: new S.withString("red") + property var i: { + let p = new Q.point; + p.x = 10 + p.y = 20 + return p + } + + property var j: 4.0 as Q.int + property var k: (4.5 / 1.5) as Q.int + property var l: 5 as Q.double + property var m: "something" as Q.var + property var n: 1 as Q.bool + property var o: Infinity as Q.int + + property var p: b as Q.size; + property var q: this as Q.size; + property var r: ({}) as Q.size; + property var s: 11 as Q.size; + property var t: Q.Component as Q.size; + property var u: Q.Qt as Q.size; +} diff --git a/tests/auto/qml/qqmllanguage/data/invokableCtors.qml b/tests/auto/qml/qqmllanguage/data/invokableCtors.qml new file mode 100644 index 0000000000..35a8d7bf08 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/invokableCtors.qml @@ -0,0 +1,12 @@ +import QtQml as QQ +import Test as VV + +QQ.QtObject { + property QQ.QtObject oo: new QQ.QtObject() + property QQ.QtObject pp: new QQ.QtObject(oo) + property VV.vv v: new VV.vv("green") + + property VV.InvokableSingleton i: new VV.InvokableSingleton(5, oo) + property VV.InvokableExtended k: new VV.InvokableExtended() + property VV.InvokableUncreatable l: new VV.InvokableUncreatable() +} diff --git a/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml b/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml new file mode 100644 index 0000000000..5bd563a288 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml @@ -0,0 +1,191 @@ +import QtQml +import TypeWithQJsonArrayProperty + +TypeWithQJsonArrayProperty { + function jsArray() { return [1, 2, 3] } + + jsonArray: jsArray() + + property list<int> concatenatedJsonArray: jsonArray.concat([4, 5, 6]) + property list<int> concatenatedJsArray: jsArray().concat([4, 5, 6]) + + property bool entriesMatch: { + var iterator = jsonArray.entries(); + for (var [index, element] of jsArray().entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + var iterator = jsArray().entries(); + for (var [index, element] of jsonArray.entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + return true; + } + + property bool jsonArrayEvery: jsonArray.every(element => element != 0) + property bool jsArrayEvery: jsArray().every(element => element != 0) + + property list<int> jsonArrayFiltered: jsonArray.filter(element => element > 2) + property list<int> jsArrayFiltered: jsArray().filter(element => element > 2) + + property int jsonArrayFind: jsonArray.find(element => element === 2) + property int jsArrayFind: jsArray().find(element => element === 2) + + property int jsonArrayFindIndex: jsonArray.findIndex(element => element === 1) + property int jsArrayFindIndex: jsArray().findIndex(element => element === 1) + + property string jsonArrayForEach + property string jsArrayForEach + + property bool jsonArrayIncludes: jsonArray.includes(3) + property bool jsArrayIncludes: jsArray().includes(3) + + property int jsonArrayIndexOf: jsonArray.indexOf(2) + property int jsArrayIndexOf: jsArray().indexOf(2) + + property string jsonArrayJoin: jsonArray.join() + property string jsArrayJoin: jsArray().join() + + property bool keysMatch: { + var iterator = jsonArray.keys(); + for (var index of jsArray().keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + var iterator = jsArray().keys(); + for (var index of jsonArray.keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + return true; + } + + property int jsonArrayLastIndexOf: jsonArray.lastIndexOf(1) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(1) + + property list<string> jsonArrayMap: jsonArray.map(element => element.toString()) + property list<string> jsArrayMap: jsArray().map(element => element.toString()) + + property int jsonArrayReduce: jsonArray.reduce((acc, element) => acc - element, 40) + property int jsArrayReduce: jsArray().reduce((acc, element) => acc - element, 40) + + property string jsonArrayReduceRight: jsonArray.reduceRight((acc, element) => acc + element.toString(), "") + property string jsArrayReduceRight: jsArray().reduceRight((acc, element) => acc + element.toString(), "") + + property list<int> jsonArraySlice: jsonArray.slice(0, 1) + property list<int> jsArraySlice: jsArray().slice(0, 1) + + property bool jsonArraySome: jsonArray.some(element => element === 1) + property bool jsArraySome: jsArray().some(element => element === 1) + + property string stringifiedLocaleJsonArray: jsonArray.toLocaleString() + property string stringifiedLocaleJsArray: jsArray().toLocaleString() + + property string stringifiedJsonArray: jsonArray.toString() + property string stringifiedJsArray: jsArray().toString() + + property bool valuesMatch: { + var iterator = jsonArray.values(); + for (var obj of jsArray().values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + var iterator = jsArray().values(); + for (var obj of jsonArray.values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + return true; + } + + // In-place mutation methods. + // Set by onCompleted if mutating jsonArray and then accessing it + // respects the mutation and the mutation behaves as for an array. + property bool jsonArrayWasCopiedWithin: false + property bool jsonArrayWasFilled: false + property bool jsonArrayWasPopped: false + property bool jsonArrayWasPushed: false + property bool jsonArrayWasReversed: false + property bool jsonArrayWasShifted: false + property bool jsonArrayWasSpliced: false + property bool jsonArrayWasUnshifted: false + property bool jsonArrayWasSorted: false + + Component.onCompleted: { + function equals(lhs, rhs) { + return lhs.toString() === rhs.toString() + } + + jsonArray.forEach(element => jsonArrayForEach += "-" + element + "-"); + jsArray().forEach(element => jsArrayForEach += "-" + element + "-"); + + var array = jsArray() + + jsonArray.copyWithin(1, 0, 1) + array.copyWithin(1, 0, 1) + jsonArrayWasCopiedWithin = equals(jsonArray, array) + + jsonArray.fill(7, 0, 1) + array.fill(7, 0, 1) + jsonArrayWasFilled = equals(jsonArray, array) + + jsonArray.pop() + array.pop() + jsonArrayWasPopped = equals(jsonArray, array) + + jsonArray.push(23) + jsonArray.push(11) + jsonArray.push(54) + jsonArray.push(42) + array.push(23) + array.push(11) + array.push(54) + array.push(42) + jsonArrayWasPushed = equals(jsonArray, array) + + jsonArray.reverse() + array.reverse() + jsonArrayWasReversed = equals(jsonArray, array) + + jsonArray.shift() + array.shift() + jsonArrayWasShifted = equals(jsonArray, array) + + jsonArray.splice(2, 1, [1, 2], 7, [1, 5]) + array.splice(2, 1, [1, 2], 7, [1, 5]) + jsonArrayWasSpliced = equals(jsonArray, array) + + jsonArray.unshift(4, 71) + array.unshift(4, 71) + jsonArrayWasUnshifted = equals(jsonArray, array) + + jsonArray.sort() + array.sort() + jsonArrayWasSorted = equals(jsonArray, array) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/nestedVectors.qml b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml new file mode 100644 index 0000000000..0bcea52133 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml @@ -0,0 +1,27 @@ +import Test +import QtQml + +NestedVectors { + id: self + + property var list1 + + Component.onCompleted: { + list1 = self.getList() + + let list2 = [] + let data1 = [] + data1.push(2) + data1.push(3) + data1.push(4) + + let data2 = [] + data2.push(5) + data2.push(6) + + list2.push(data1) + list2.push(data2) + + self.setList(list2) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml b/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml new file mode 100644 index 0000000000..32765895a0 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml @@ -0,0 +1,14 @@ +import QtQml + +QtObject { + id: root + + property int changes: 0 + + property list<int> numbers: [1, 2, 3, 4, 5] + onNumbersChanged: ++changes + + property var one: numbers.shift.bind([1,2,3])() + + Component.onCompleted: root.numbers.shift() +} diff --git a/tests/auto/qml/qqmllanguage/data/typedObjectList.qml b/tests/auto/qml/qqmllanguage/data/typedObjectList.qml new file mode 100644 index 0000000000..7e6f6e8dd9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typedObjectList.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + property var b; + property Component c: QtObject {} + + function returnList(a: Component) : list<Component> { return [a] } + + Component.onCompleted: b = { b: returnList(c) } +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index ffff0a6979..526cca4b5b 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -174,6 +174,14 @@ void registerTypes() qmlRegisterTypesAndRevisions<NonSingleton>("EnumScopeTest", 1); qmlRegisterTypesAndRevisions<EnumProviderSingletonQml>("EnumScopeTest", 1); + qmlRegisterTypesAndRevisions<TypeWithQJsonArrayProperty>("TypeWithQJsonArrayProperty", 1); + qmlRegisterTypesAndRevisions< + InvokableSingleton, + InvokableExtended, + InvokableUncreatable, + InvokableValueType + >("Test", 1); + qmlRegisterTypesAndRevisions<NestedVectors>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index bcf02c1cf9..ce6abf3504 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -6,6 +6,7 @@ #include <QtCore/qobject.h> #include <QtCore/qrect.h> #include <QtCore/qdatetime.h> +#include <QtCore/qjsonarray.h> #include <QtGui/qtransform.h> #include <QtGui/qcolor.h> #include <QtGui/qvector2d.h> @@ -2942,4 +2943,104 @@ public: } }; +class TypeWithQJsonArrayProperty : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QJsonArray jsonArray READ jsonArray WRITE setJsonArray NOTIFY jsonArrayChanged) + +public: + TypeWithQJsonArrayProperty(QObject *parent = nullptr) : QObject(parent) {} + + const QJsonArray& jsonArray() { return m_jsonArray; } + void setJsonArray(const QJsonArray& a) { m_jsonArray = a; } + +signals: + void jsonArrayChanged(); + +private: + QJsonArray m_jsonArray; +}; + +class InvokableSingleton : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + InvokableSingleton() = default; + Q_INVOKABLE InvokableSingleton(int a, QObject *parent) : QObject(parent), m_a(a) {} + + int m_a = 0; +}; + +class InvokableExtension : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE InvokableExtension(QObject *parent = nullptr) : QObject(parent) {} +}; + +class InvokableExtended : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(InvokableExtension) + +public: + Q_INVOKABLE InvokableExtended() = default; +}; + +class InvokableUncreatable : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("no") + +public: + Q_INVOKABLE InvokableUncreatable() = default; +}; + +class InvokableValueType +{ + Q_GADGET + QML_VALUE_TYPE(vv) +public: + Q_INVOKABLE InvokableValueType() = default; + Q_INVOKABLE InvokableValueType(const QString &s) : m_s(s) {} + QString m_s; +}; + +class NestedVectors : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + NestedVectors(QObject *parent = nullptr) : QObject(parent) + { + std::vector<int> data; + data.push_back(1); + data.push_back(2); + data.push_back(3); + m_list.push_back(data); + data.clear(); + data.push_back(4); + data.push_back(5); + m_list.push_back(data); + } + + Q_INVOKABLE std::vector<std::vector<int>> getList() + { + return m_list; + } + + Q_INVOKABLE void setList(std::vector<std::vector<int>> list) + { + m_list = list; + } + +private: + std::vector<std::vector<int>> m_list; +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 61295ec940..b40ce61c04 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -410,7 +410,9 @@ private slots: void objectAndGadgetMethodCallsRejectThisObject(); void objectAndGadgetMethodCallsAcceptThisObject(); + void asValueType(); + void asValueTypeGood(); void longConversion(); @@ -452,6 +454,14 @@ private slots: void overrideDefaultProperty(); void enumScopes(); + void typedObjectList(); + void invokableCtors(); + + void jsonArrayPropertyBehavesLikeAnArray(); + + void nestedVectors(); + void optimizedSequenceShift(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -5409,24 +5419,30 @@ void tst_qqmllanguage::namespacedPropertyTypes() void tst_qqmllanguage::qmlTypeCanBeResolvedByName_data() { QTest::addColumn<QUrl>("componentUrl"); + QTest::addColumn<QString>("name"); // Built-in C++ types - QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml"); - QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml"); + QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml") + << QStringLiteral("QtQuick/Item"); + QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml") + << QStringLiteral("QtQuick/Item"); // Composite types with a qmldir - QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml"); - QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml"); + QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml") + << QStringLiteral("SimpleType"); + QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml") + << QStringLiteral("SimpleType"); } void tst_qqmllanguage::qmlTypeCanBeResolvedByName() { QFETCH(QUrl, componentUrl); + QFETCH(QString, name); QQmlEngine engine; QQmlComponent component(&engine, componentUrl); VERIFY_ERRORS(0); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, "[object Object]"); // a bit crude, but it will do + QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(name)); QScopedPointer<QObject> o(component.create()); QVERIFY(!o.isNull()); @@ -8057,7 +8073,60 @@ void tst_qqmllanguage::asValueType() QTest::ignoreMessage( QtWarningMsg, + "Could not find any constructor for value type QQmlRectFValueType " + "to call with value undefined"); + + QTest::ignoreMessage( + QtWarningMsg, qPrintable(url.toString() + ":7:5: Unable to assign [undefined] to QRectF"_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":10: Coercing a value to QtQml.Base/point using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":14: Coercing between incompatible value types mistakenly " + "yields null rather than undefined. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent this."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":15: Coercing from instances of object types to value " + "types mistakenly yields null rather than undefined. Add " + "'pragma ValueTypeBehavior: Assertable' to prevent " + "this."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":16: Coercing a value to QtQml.Base/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":11: Coercing a value to StaticTest/withString using a " + "type assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlSizeFValueType to call " + "with value 11"); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":18: Coercing a value to QtQml.Base/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":19: Coercing a value to QtQml.Base/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + QScopedPointer<QObject> o(c.create()); QCOMPARE(o->property("a"), QVariant()); @@ -8080,6 +8149,84 @@ void tst_qqmllanguage::asValueType() const QVariant string = o->property("g"); QCOMPARE(string.metaType(), QMetaType::fromType<QString>()); QCOMPARE(string.toString(), u"green"); + + const QVariant p = o->property("p"); + QCOMPARE(p.metaType(), QMetaType::fromType<std::nullptr_t>()); + + const QVariant q = o->property("q"); + QCOMPARE(q.metaType(), QMetaType::fromType<std::nullptr_t>()); + + const QVariant r = o->property("r"); + QCOMPARE(r.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(r.value<QSizeF>(), QSizeF()); + + const QVariant s = o->property("s"); + QCOMPARE(s.metaType(), QMetaType()); + + const QVariant t = o->property("t"); + QCOMPARE(t.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(t.value<QSizeF>(), QSizeF()); + + const QVariant u = o->property("u"); + QCOMPARE(u.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(u.value<QSizeF>(), QSizeF()); +} + +void tst_qqmllanguage::asValueTypeGood() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("asValueTypeGood.qml"); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":7:5: Unable to assign [undefined] to QRectF"_L1)); + QScopedPointer<QObject> o(c.create()); + + QCOMPARE(o->property("a"), QVariant()); + QCOMPARE(o->property("b").value<QRectF>(), QRectF()); + QVERIFY(!o->property("c").toBool()); + + const QRectF rect(1, 2, 3, 4); + o->setProperty("a", QVariant(rect)); + QCOMPARE(o->property("b").value<QRectF>(), rect); + QVERIFY(o->property("c").toBool()); + + QVERIFY(!o->property("d").toBool()); + QVERIFY(!o->property("e").isValid()); + QVERIFY(!o->property("f").isValid()); + + const QVariant string = o->property("g"); + QCOMPARE(string.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(string.toString(), u"green"); + + const ValueTypeWithString withString = o->property("h").value<ValueTypeWithString>(); + QCOMPARE(withString.toString(), u"red"); + + const QPointF point = o->property("i").value<QPointF>(); + QCOMPARE(point.x(), 10.0); + QCOMPARE(point.y(), 20.0); + + const QVariant j = o->property("j"); + QCOMPARE(j.metaType(), QMetaType::fromType<int>()); + QCOMPARE(j.toInt(), 4); + + QVERIFY(!o->property("k").isValid()); + QVERIFY(!o->property("l").isValid()); + + const QVariant m = o->property("m"); + QCOMPARE(m.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(m.toString(), u"something"); + + QVERIFY(!o->property("n").isValid()); + QVERIFY(!o->property("o").isValid()); + QVERIFY(!o->property("p").isValid()); + QVERIFY(!o->property("q").isValid()); + QVERIFY(!o->property("r").isValid()); + QVERIFY(!o->property("s").isValid()); + QVERIFY(!o->property("t").isValid()); + QVERIFY(!o->property("u").isValid()); } void tst_qqmllanguage::typedEnums_data() @@ -8642,6 +8789,148 @@ void tst_qqmllanguage::enumScopes() QCOMPARE(o->property("singletonUnscopedValue").toInt(), int(EnumProviderSingleton::Expected::Value)); } +void tst_qqmllanguage::typedObjectList() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("typedObjectList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QJSValue b = o->property("b").value<QJSValue>(); + auto list = qjsvalue_cast<QQmlListProperty<QQmlComponent>>(b.property(QStringLiteral("b"))); + + QCOMPARE(list.count(&list), 1); + QVERIFY(list.at(&list, 0) != nullptr); +} + +void tst_qqmllanguage::jsonArrayPropertyBehavesLikeAnArray() { + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("jsonArrayProperty.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("concatedJsonArray"), o->property("concatedJsArray")); + QVERIFY(o->property("entriesMatch").toBool()); + QCOMPARE(o->property("jsonArrayEvery"), o->property("jsArrayEvery")); + QCOMPARE(o->property("jsonArrayFiltered"), o->property("jsArrayFiltered")); + QCOMPARE(o->property("jsonArrayFind"), o->property("jsArrayFind")); + QCOMPARE(o->property("jsonArrayFindIndex"), o->property("jsArrayFindIndex")); + QCOMPARE(o->property("jsonArrayForEach"), o->property("jsArrayForEach")); + QCOMPARE(o->property("jsonArrayIncludes"), o->property("jsArrayIncludes")); + QCOMPARE(o->property("jsonArrayIndexOf"), o->property("jsArrayIndexOf")); + QCOMPARE(o->property("jsonArrayJoin"), o->property("jsArrayJoin")); + QVERIFY(o->property("keysMatch").toBool()); + QCOMPARE(o->property("jsonArrayLastIndexOf"), o->property("jsArrayLastIndexOf")); + QCOMPARE(o->property("jsonArrayMap"), o->property("jsArrayMap")); + QCOMPARE(o->property("jsonArrayReduce"), o->property("jsArrayReduce")); + QCOMPARE(o->property("jsonArrayReduceRight"), o->property("jsArrayReduceRight")); + QCOMPARE(o->property("jsonArraySlice"), o->property("jsArraySlice")); + QCOMPARE(o->property("jsonArraySome"), o->property("jsArraySome")); + QCOMPARE(o->property("stringifiedLocaleJsonArray"), o->property("stringifiedLocaleJsArray")); + QCOMPARE(o->property("stringifiedJsonArray"), o->property("stringifiedJsArray")); + QVERIFY(o->property("valuesMatch").toBool()); + + QVERIFY(o->property("jsonArrayWasCopiedWithin").toBool()); + QVERIFY(o->property("jsonArrayWasFilled").toBool()); + QVERIFY(o->property("jsonArrayWasPopped").toBool()); + QVERIFY(o->property("jsonArrayWasPushed").toBool()); + QVERIFY(o->property("jsonArrayWasReversed").toBool()); + QVERIFY(o->property("jsonArrayWasShifted").toBool()); + QVERIFY(o->property("jsonArrayWasSpliced").toBool()); + QVERIFY(o->property("jsonArrayWasUnshifted").toBool()); + QEXPECT_FAIL( + "", + "The sort method for sequences will not currently work with QJsonArray. See QTBUG-125400.", + Continue + ); + QVERIFY(o->property("jsonArrayWasSorted").toBool()); +} + +void tst_qqmllanguage::invokableCtors() +{ + QQmlEngine e; + + const QUrl url = testFileUrl("invokableCtors.qml"); + + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + const QString urlString = url.toString(); + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":9: You are calling a Q_INVOKABLE constructor of " + "InvokableSingleton which is a singleton in QML.")); + + // Extended types look like types without any constructors. + // Therefore they aren't even FunctionObjects. + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":10: TypeError: Type error")); + + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":11: You are calling a Q_INVOKABLE constructor of " + "InvokableUncreatable which is uncreatable in QML.")); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QObject *oo = qvariant_cast<QObject *>(o->property("oo")); + QVERIFY(oo); + QObject *pp = qvariant_cast<QObject *>(o->property("pp")); + QVERIFY(pp); + QCOMPARE(pp->parent(), oo); + + InvokableValueType vv = qvariant_cast<InvokableValueType>(o->property("v")); + QCOMPARE(vv.m_s, "green"); + + InvokableSingleton *i = qvariant_cast<InvokableSingleton *>(o->property("i")); + QVERIFY(i); + QCOMPARE(i->m_a, 5); + QCOMPARE(i->parent(), oo); + + QVariant k = o->property("k"); + QCOMPARE(k.metaType(), QMetaType::fromType<InvokableExtended *>()); + QCOMPARE(k.value<InvokableExtended *>(), nullptr); + + InvokableUncreatable *l = qvariant_cast<InvokableUncreatable *>(o->property("l")); + QVERIFY(l); +} + +void tst_qqmllanguage::nestedVectors() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("nestedVectors.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + NestedVectors *n = qobject_cast<NestedVectors *>(o.data()); + QVERIFY(n); + + const std::vector<std::vector<int>> expected1 { { 1, 2, 3 }, { 4, 5 } }; + const QVariant list1 = n->property("list1"); + QCOMPARE(list1.metaType(), QMetaType::fromType<std::vector<std::vector<int>>>()); + QCOMPARE(list1.value<std::vector<std::vector<int>>>(), expected1); + + const std::vector<std::vector<int>> expected2 { { 2, 3, 4 }, { 5, 6 } }; + QCOMPARE(n->getList(), expected2); +} + +void tst_qqmllanguage::optimizedSequenceShift() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("optimizedSequenceShift.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("changes").toInt(), 2); + + const QVariant one = o->property("one"); + QCOMPARE(one.metaType(), QMetaType::fromType<int>()); + QCOMPARE(one.toInt(), 1); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp index dbab663b22..a7ddf79ad5 100644 --- a/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp +++ b/tests/auto/qml/qqmllocale/tst_qqmllocale.cpp @@ -478,10 +478,12 @@ void tst_qqmllocale::toString_data() QTest::newRow(qPrintable(functionCallScript)) << "ar" << functionCallScript << "١٦" << QString(); functionCallScript = "locale.toString(new Date(2022, 7, 16), Locale.ShortFormat)"; - QTest::newRow(qPrintable(functionCallScript)) << "en_AU" << functionCallScript << "16/8/22 12:00 AM" << QString(); + QTest::newRow(qPrintable(functionCallScript)) + << "en_AU" << functionCallScript << "16/8/22 12:00 am" << QString(); functionCallScript = "locale.toString(new Date(2022, 7, 16, 1, 23, 4), Locale.ShortFormat)"; - QTest::newRow(qPrintable(functionCallScript)) << "en_AU" << functionCallScript << "16/8/22 1:23 AM" << QString(); + QTest::newRow(qPrintable(functionCallScript)) + << "en_AU" << functionCallScript << "16/8/22 1:23 am" << QString(); } void tst_qqmllocale::toString() diff --git a/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp b/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp index dea781cc17..04c2a5bfdb 100644 --- a/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp +++ b/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp @@ -769,8 +769,16 @@ void checkBuiltinTypes() template<typename T> void checkNamedBuiltin(const QString &name) { - QCOMPARE(QQmlMetaType::qmlType("QML/" + name, QTypeRevision::fromVersion(1, 0)), - QQmlMetaType::qmlType(QMetaType::fromType<T>())); + const QQmlType expected = QQmlMetaType::qmlType(QMetaType::fromType<T>()); + const QQmlType actual = QQmlMetaType::qmlType("QML/" + name, QTypeRevision::fromVersion(1, 0)); + if (actual != expected) { + qWarning() << Q_FUNC_INFO << "looking for" << name; + qWarning() << "found" << actual.module() << actual.elementName() << actual.version() + << actual.typeId(); + qWarning() << "expected" << expected.module() << expected.elementName() + << expected.version() << expected.typeId(); + QFAIL("mismatch"); + } } template<typename T> diff --git a/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp b/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp index 0e1bb1abc3..e57eb1b65a 100644 --- a/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp +++ b/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp @@ -131,7 +131,9 @@ void registerStaticPlugin(const char *uri) PluginType::metaData.append(char(QT_VERSION_MAJOR)); PluginType::metaData.append(char(QT_VERSION_MINOR)); PluginType::metaData.append(char(qPluginArchRequirements())); +#if QT_CONFIG(cborstreamwriter) PluginType::metaData.append(QCborValue(QCborMap::fromJsonObject(md)).toCbor()); +#endif auto rawMetaDataFunctor = []() -> QPluginMetaData { return {reinterpret_cast<const uchar *>(PluginType::metaData.constData()), size_t(PluginType::metaData.size())}; diff --git a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp index eb8c6c260f..0a8411ddcf 100644 --- a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp +++ b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp @@ -34,6 +34,10 @@ private slots: void codeLocationsWithContinuationStringLiteral_data(); void noSubstitutionTemplateLiteral(); void templateLiteral(); + void numericSeparator_data(); + void numericSeparator(); + void invalidNumericSeparator_data(); + void invalidNumericSeparator(); void leadingSemicolonInClass(); void templatedReadonlyProperty(); void qmlImportInJS(); @@ -496,6 +500,74 @@ void tst_qqmlparser::templateLiteral() QVERIFY(e); } +void tst_qqmlparser::numericSeparator_data() { + QTest::addColumn<QString>("code"); + QTest::addColumn<double>("expected_value"); + + QTest::newRow("Separator in decimal literal") << "1_000_000_000" << 1000000000.0; + QTest::newRow("Separator in fractional part") << "1000.22_33" << 1000.2233; + QTest::newRow("Separator in exponent part") << "1e1_0_0" << std::pow(10, 100); + QTest::newRow("Separator in positive exponent part") << "1e+1_0_0" << 1e100; + QTest::newRow("Separator in negative exponent part") << "1e-1_0_0" << 1e-100; + QTest::newRow("Separator in binary literal with b prefix") << "0b1010_0001_1000_0101" << static_cast<double>(0b1010000110000101); + QTest::newRow("Separator in binary literal with B prefix") << "0B01_10_01_10" << static_cast<double>(0b01100110); + QTest::newRow("Separator in octal literal with o prefix") << "0o1234_5670" << static_cast<double>(012345670); + QTest::newRow("Separator in octal literal with O prefix") << "0O7777_0000" << static_cast<double>(077770000); + QTest::newRow("Separator in hex literal with x prefix") << "0xA0_B0_C0" << static_cast<double>(0xA0B0C0); + QTest::newRow("Separator in hex literal with X prefix") << "0X1000_AAAA" << static_cast<double>(0x1000AAAA); +} + +void tst_qqmlparser::numericSeparator() { + using namespace QQmlJS; + + QFETCH(QString, code); + QFETCH(double, expected_value); + + QQmlJS::Engine engine; + + QQmlJS::Lexer lexer(&engine); + lexer.setCode(code, 1); + + QQmlJS::Parser parser(&engine); + QVERIFY(parser.parseExpression()); + + AST::ExpressionNode *expression = parser.expression(); + QVERIFY(expression); + + auto *literal = QQmlJS::AST::cast<QQmlJS::AST::NumericLiteral *>(expression); + QVERIFY(literal); + + QCOMPARE(literal->value, expected_value); + QCOMPARE(literal->firstSourceLocation().begin(), 0u); + QCOMPARE(literal->lastSourceLocation().end(), quint32(code.size())); +} + +void tst_qqmlparser::invalidNumericSeparator_data() { + QTest::addColumn<QString>("code"); + QTest::addColumn<QString>("error"); + + QTest::newRow("Trailing numeric separator") << "1_" << "A trailing numeric separator is not allowed in numeric literals"; + QTest::newRow("Multiple numeric separators") << "1__2" << "There can be at most one numeric separator beetwen digits"; +} + +void tst_qqmlparser::invalidNumericSeparator() { + using namespace QQmlJS; + + QFETCH(QString, code); + QFETCH(QString, error); + + QQmlJS::Engine engine; + + QQmlJS::Lexer lexer(&engine); + lexer.setCode(code, 1); + + QQmlJS::Parser parser(&engine); + QVERIFY(!parser.parseExpression()); + + QVERIFY(lexer.errorCode() != Lexer::NoError); + QCOMPARE(lexer.errorMessage(), error); +} + void tst_qqmlparser::leadingSemicolonInClass() { QQmlJS::Engine engine; diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp index 710bbce17a..9fea41104d 100644 --- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp +++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp @@ -57,8 +57,10 @@ private slots: void alpha(); void tint(); void color(); +#if QT_CONFIG(desktopservices) void openUrlExternally(); void openUrlExternally_pragmaLibrary(); +#endif void md5(); void createComponent(); void createComponent_pragmaLibrary(); @@ -613,6 +615,7 @@ public slots: void noteCall(const QUrl &url) { called++; last = url; } }; +#if QT_CONFIG(desktopservices) void tst_qqmlqt::openUrlExternally() { MyUrlHandler handler; @@ -659,6 +662,7 @@ void tst_qqmlqt::openUrlExternally_pragmaLibrary() QCOMPARE(handler.called,2); QCOMPARE(handler.last, htmlTestFile); } +#endif void tst_qqmlqt::md5() { diff --git a/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp b/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp index a6c61abd57..495f7044f6 100644 --- a/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp +++ b/tests/auto/qml/qqmltimer/tst_qqmltimer.cpp @@ -1,14 +1,18 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include <QtTest/QSignalSpy> -#include <qtest.h> -#include <QtQml/qqmlengine.h> -#include <QtQml/qqmlcomponent.h> -#include <QtQml/private/qqmltimer_p.h> -#include <QtQuick/qquickitem.h> -#include <QDebug> -#include <QtCore/QPauseAnimation> + #include <private/qabstractanimation_p.h> +#include <private/qqmltimer_p.h> + +#include <QtQuick/qquickitem.h> + +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlengine.h> + +#include <QtTest/qsignalspy.h> +#include <QtTest/qtest.h> + +#include <QtCore/qpauseanimation.h> void consistentWait(int ms) { diff --git a/tests/auto/qml/qqmlvaluetypes/data/constructors.qml b/tests/auto/qml/qqmlvaluetypes/data/constructors.qml new file mode 100644 index 0000000000..d94d6d8ad4 --- /dev/null +++ b/tests/auto/qml/qqmlvaluetypes/data/constructors.qml @@ -0,0 +1,14 @@ +import QtQuick as Q + +Q.QtObject { + property var point: new Q.point() + property var size: new Q.size() + property var rect: new Q.rect() + property var color: new Q.color() + property var vector2d: new Q.vector2d() + property var vector3d: new Q.vector3d() + property var vector4d: new Q.vector4d() + property var quaternion: new Q.quaternion() + property var matrix4x4: new Q.matrix4x4() + property var font: new Q.font() +} diff --git a/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml b/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml index c28901956d..1827b57ca9 100644 --- a/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml +++ b/tests/auto/qml/qqmlvaluetypes/data/matrix4x4_invokables.qml @@ -1,4 +1,4 @@ -import QtQuick 2.0 +import QtQuick Item { property bool success: false @@ -6,6 +6,7 @@ Item { property variant m1: Qt.matrix4x4(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4) property variant m2: Qt.matrix4x4(5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8) property variant m3: Qt.matrix4x4(123,22,6,42,55,54,67,77,777,1,112,22,55,6696,77,777) + property matrix4x4 m4: PlanarTransform.fromAffineMatrix(1, 2, 3, 4, 5, 6) property variant v1: Qt.vector4d(1,2,3,4) property variant v2: Qt.vector3d(1,2,3) property real factor: 2.23 @@ -101,6 +102,7 @@ Item { if (m1.transposed() != Qt.matrix4x4(1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4)) success = false; if (m1.fuzzyEquals(m2)) success = false; if (!m1.fuzzyEquals(m2, 10)) success = false; + if (m4 != Qt.matrix4x4(1, 3, 0, 5, 2, 4, 0, 6, 0, 0, 1, 0, 0, 0, 0, 1)) success = false; if (!testTransformation()) success = false; if (!testMatrixMapping()) success = false; } diff --git a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp index 2634044238..ea521053ae 100644 --- a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp +++ b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp @@ -78,6 +78,7 @@ private slots: void writeBackOnFunctionCall(); void valueTypeConversions(); void readReferenceOnGetOwnProperty(); + void constructors(); private: QQmlEngine engine; @@ -1832,6 +1833,28 @@ void tst_qqmlvaluetypes::readReferenceOnGetOwnProperty() QVERIFY(o->property("allo").toBool()); } +void tst_qqmlvaluetypes::constructors() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("constructors.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("point"), QVariant(QPointF())); + QCOMPARE(o->property("size"), QVariant(QSizeF())); + QCOMPARE(o->property("rect"), QVariant(QRectF())); + QCOMPARE(o->property("color"), QVariant(QColor())); + QCOMPARE(o->property("vector2d"), QVariant(QVector2D())); + QCOMPARE(o->property("vector3d"), QVariant(QVector3D())); + QCOMPARE(o->property("vector4d"), QVariant(QVector4D())); + QCOMPARE(o->property("quaternion"), QVariant(QQuaternion())); + QCOMPARE(o->property("matrix4x4"), QVariant(QMatrix4x4())); + QCOMPARE(o->property("font"), QVariant(QFont())); + +} + #undef CHECK_TYPE_IS_NOT_VALUETYPE QTEST_MAIN(tst_qqmlvaluetypes) diff --git a/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp b/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp index eebe4a6c05..bb6e59cb17 100644 --- a/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp +++ b/tests/auto/qml/qqmlxmllistmodel/tst_qqmlxmllistmodel.cpp @@ -321,14 +321,12 @@ void tst_QQmlXmlListModel::headers() QTRY_COMPARE_WITH_TIMEOUT(qvariant_cast<QQmlXmlListModel::Status>(model->property("status")), QQmlXmlListModel::Error, 10000); - QVariantMap expectedHeaders; - expectedHeaders["Accept"] = "application/xml,*/*"; + QLatin1String expectedAcceptHeader = "application/xml,*/*"_L1; - QCOMPARE(factory.lastSentHeaders.size(), expectedHeaders.size()); - for (auto it = expectedHeaders.cbegin(), end = expectedHeaders.cend(); it != end; ++it) { - QVERIFY(factory.lastSentHeaders.contains(it.key())); - QCOMPARE(factory.lastSentHeaders[it.key()].toString(), it.value().toString()); - } + QCOMPARE(factory.lastSentHeaders.size(), 1); + QVariant acceptHeader = factory.lastSentHeaders["accept"]; + QVERIFY(acceptHeader.isValid()); + QCOMPARE(acceptHeader.toString(), expectedAcceptHeader); } void tst_QQmlXmlListModel::source() diff --git a/tests/auto/qml/qv4estable/tst_qv4estable.cpp b/tests/auto/qml/qv4estable/tst_qv4estable.cpp index 45df62b23e..7d137ae7d2 100644 --- a/tests/auto/qml/qv4estable/tst_qv4estable.cpp +++ b/tests/auto/qml/qv4estable/tst_qv4estable.cpp @@ -18,7 +18,7 @@ void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() QV4::ESTable estable; // Fill the ESTable with values so it is at max capacity. - QCOMPARE_EQ(estable.m_capacity, 8); + QCOMPARE_EQ(estable.m_capacity, 8U); for (uint i = 0; i < estable.m_capacity; ++i) { estable.set(QV4::Value::fromUInt32(i), QV4::Value::fromUInt32(i)); } @@ -27,8 +27,8 @@ void tst_qv4estable::checkRemoveAvoidsHeapBufferOverflow() for (uint i = 0; i < estable.m_capacity; ++i) { QVERIFY(estable.m_keys[i].sameValueZero(QV4::Value::fromUInt32(i))); } - QCOMPARE_EQ(estable.m_capacity, 8); - QCOMPARE_EQ(estable.m_size, 8); + QCOMPARE_EQ(estable.m_capacity, 8U); + QCOMPARE_EQ(estable.m_size, 8U); // Remove the first item from the set to verify that asan does not trip. // Relies on the CI platform propagating asan flag to all tests. diff --git a/tests/auto/qml/qv4mm/data/simpleObject.qml b/tests/auto/qml/qv4mm/data/simpleObject.qml new file mode 100644 index 0000000000..8fc36a40da --- /dev/null +++ b/tests/auto/qml/qv4mm/data/simpleObject.qml @@ -0,0 +1,3 @@ +import QtQml + +QtObject {} diff --git a/tests/auto/qml/qv4mm/tst_qv4mm.cpp b/tests/auto/qml/qv4mm/tst_qv4mm.cpp index 28926f02f3..5bcdcd4624 100644 --- a/tests/auto/qml/qv4mm/tst_qv4mm.cpp +++ b/tests/auto/qml/qv4mm/tst_qv4mm.cpp @@ -12,6 +12,7 @@ #include <private/qqmlengine_p.h> #include <private/qv4identifiertable_p.h> #include <private/qv4arraydata_p.h> +#include <private/qqmlcomponentattached_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -34,6 +35,8 @@ private slots: void cleanInternalClasses(); void createObjectsOnDestruction(); void sharedInternalClassDataMarking(); + void gcTriggeredInOnDestroyed(); + void weakValuesAssignedAfterThePhaseThatShouldHandleWeakValues(); }; tst_qv4mm::tst_qv4mm() @@ -387,6 +390,118 @@ void tst_qv4mm::sharedInternalClassDataMarking() QCOMPARE(val.toUInt32(), 42u); } +void tst_qv4mm::gcTriggeredInOnDestroyed() +{ + QQmlEngine engine; + QV4::ExecutionEngine &v4 = *engine.handle(); + + QPointer<QObject> testObject = new QObject; // unparented, will be deleted + auto cleanup = qScopeGuard([&]() { + if (testObject) + testObject->deleteLater(); + }); + + QQmlComponent component(&engine, testFileUrl("simpleObject.qml")); + auto toBeCollected = component.create(); + QVERIFY(toBeCollected); + QJSEngine::setObjectOwnership(toBeCollected, QJSEngine::JavaScriptOwnership); + QV4::QObjectWrapper::ensureWrapper(&v4, toBeCollected); + QVERIFY(qmlEngine(toBeCollected)); + QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(toBeCollected); + QVERIFY(attached); + + + QV4::Scope scope(v4.rootContext()); + QCOMPARE(v4.memoryManager->gcBlocked, QV4::MemoryManager::Unblocked); + + + + // let the gc run up to CallDestroyObjects + auto sm = v4.memoryManager->gcStateMachine.get(); + sm->reset(); + v4.memoryManager->gcBlocked = QV4::MemoryManager::NormalBlocked; + while (sm->state != QV4::GCState::CallDestroyObjects && sm->state != QV4::GCState::Invalid) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QCOMPARE(sm->state, QV4::GCState::CallDestroyObjects); + + QV4::ScopedValue val(scope); + bool calledOnDestroyed = false; + auto con = connect(attached, &QQmlComponentAttached::destruction, this, [&]() { + calledOnDestroyed = true; + // we trigger uncommon code paths: + // create ObjectWrapper in destroyed hadnler + auto ddata = QQmlData::get(testObject.get(), false); + QVERIFY(!ddata); // we don't have ddata yet (otherwise we'd already have an object wrapper) + val = QV4::QObjectWrapper::wrap(&v4, testObject.get()); + QJSEngine::setObjectOwnership(testObject, QJSEngine::JavaScriptOwnership); + + // and also try to trigger a force gc completion + bool gcComplete = v4.memoryManager->tryForceGCCompletion(); + QVERIFY(!gcComplete); + }); + while (!calledOnDestroyed && sm->state != QV4::GCState::Invalid) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QVERIFY(!QTest::currentTestFailed()); + QObject::disconnect(con); + QVERIFY(calledOnDestroyed); + + bool gcComplete = v4.memoryManager->tryForceGCCompletion(); + QVERIFY(gcComplete); + val = QV4::Value::undefinedValue(); // no longer keep a reference on the stack + QCOMPARE(sm->state, QV4::GCState::Invalid); + QVERIFY(testObject); // must not have be deleted, referenced by val + + gc(v4); // run another gc cycle + QVERIFY(!testObject); // now collcted by gc +} +void tst_qv4mm::weakValuesAssignedAfterThePhaseThatShouldHandleWeakValues() +{ + QObject testObject; + QV4::ExecutionEngine v4; + + QCOMPARE(v4.memoryManager->gcBlocked, QV4::MemoryManager::Unblocked); + + + + // let the gc run up to CallDestroyObjects + auto sm = v4.memoryManager->gcStateMachine.get(); + sm->reset(); + v4.memoryManager->gcBlocked = QV4::MemoryManager::NormalBlocked; + + + // run just before the sweeping face + while (sm->state != QV4::GCState::DoSweep && sm->state != QV4::GCState::Invalid) { + QV4::GCStateInfo& stateInfo = sm->stateInfoMap[int(sm->state)]; + sm->state = stateInfo.execute(sm, sm->stateData); + } + QCOMPARE(sm->state, QV4::GCState::DoSweep); + + { + // simulate code accessing the object wrapper for an object + QV4::Scope scope(v4.rootContext()); + QV4::ScopedValue value(scope); + value = QV4::QObjectWrapper::wrap(&v4, &testObject); + // let it go out of scope before any stack re-scanning could happen + } + + bool gcComplete = v4.memoryManager->tryForceGCCompletion(); + QVERIFY(gcComplete); + + auto ddata = QQmlData::get(&testObject); + QVERIFY(ddata); + if (ddata->jsWrapper.isUndefined()) { + // it's in principle valid for the wrapper to be reset, though the current + // implementation doesn't do it, and it requires some care + qWarning("Double-check the handling of weak values and object wrappers in the gc"); + return; + } + QVERIFY(ddata->jsWrapper.valueRef()->heapObject()->inUse()); +} + QTEST_MAIN(tst_qv4mm) #include "tst_qv4mm.moc" |