diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sfx2/qa/complex | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream/4%7.4.7.tar.xz libreoffice-upstream/4%7.4.7.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sfx2/qa/complex')
19 files changed, 4819 insertions, 0 deletions
diff --git a/sfx2/qa/complex/sfx2/DocumentEvents.java b/sfx2/qa/complex/sfx2/DocumentEvents.java new file mode 100644 index 000000000..32c7ba5ae --- /dev/null +++ b/sfx2/qa/complex/sfx2/DocumentEvents.java @@ -0,0 +1,221 @@ +package complex.sfx2; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openoffice.test.OfficeConnection; +import org.openoffice.test.tools.OfficeDocument; + +import com.sun.star.document.DocumentEvent; +import com.sun.star.document.XDocumentEventBroadcaster; +import com.sun.star.document.XDocumentEventListener; +import com.sun.star.lang.EventObject; +import com.sun.star.lang.XEventListener; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.CloseVetoException; +import com.sun.star.util.XCloseListener; +import com.sun.star.util.XCloseable; + +public class DocumentEvents +{ + private static final OfficeConnection m_connection = new OfficeConnection(); + + @BeforeClass + public static void setUpConnection() throws Exception + { + m_connection.setUp(); + } + + + @AfterClass + public static void tearDownConnection() throws InterruptedException, com.sun.star.uno.Exception + { + m_connection.tearDown(); + } + + @Before + public void beforeTest() throws Exception + { + final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface( + XMultiServiceFactory.class, m_connection.getComponentContext().getServiceManager() ); + m_document = OfficeDocument.blankTextDocument( xMSF1 ); + } + + @After + public void afterTest() + { + if ( m_document != null ) + { + assertTrue( "closing the test document failed", m_document.close() ); + m_document = null; + } + } + + /** + * sets up the environment for a test which checks the behavior upon closing a doc + */ + private void impl_setupDocCloseTest() + { + m_observedCloseEvents.clear(); + + final XDocumentEventBroadcaster docEventBroadcaster = UnoRuntime.queryInterface( + XDocumentEventBroadcaster.class, m_document.getDocument() ); + docEventBroadcaster.addDocumentEventListener( new DocumentEventListener() ); + + final XCloseable docCloseable = UnoRuntime.queryInterface( XCloseable.class, + m_document.getDocument() ); + docCloseable.addCloseListener( new CloseListener() ); + + m_document.getDocument().addEventListener( new DocDisposeListener() ); + } + + /** + * sets up the environment for a test which checks the behavior upon closing a doc + */ + private void impl_tearDownDocCloseTest( final String i_docCloseMethod ) + { + synchronized( m_document ) + { + try + { + m_document.wait(10000); + } + catch (InterruptedException ex) + { + // don't continue the test if somebody interrupted us ... + return; + } + } + + m_document = null; + synchronized( m_observedCloseEvents ) + { + assertArrayEquals( + "wrong order of events when closing a doc " + i_docCloseMethod, + new CloseEventType[] { CloseEventType.OnUnload, CloseEventType.NotifyClosing, CloseEventType.Disposing }, + m_observedCloseEvents.toArray( new CloseEventType[0] ) + ); + } + } + + @Test + public void testCloseWinEvents() throws Exception + { + impl_setupDocCloseTest(); + m_document.getCurrentView().dispatch( ".uno:CloseWin" ); + impl_tearDownDocCloseTest( "via .uno:CloseWin" ); + } + + //@Test + public void testCloseDocEvents() throws Exception + { + impl_setupDocCloseTest(); + m_document.getCurrentView().dispatch( ".uno:CloseDoc" ); + impl_tearDownDocCloseTest( "via .uno:CloseDoc" ); + } + + //@Test + public void testCloseByAPI() + { + impl_setupDocCloseTest(); + // closing the doc by API is synchronous, so do this in a separate thread, else we will get a deadlock + // when the document tries to call back our listener (well, I admit I didn't understand *why* we get this + // deadlock ... :-\ ) + (new DocCloser()).start(); + impl_tearDownDocCloseTest( "by API" ); + } + + private class DocumentEventListener implements XDocumentEventListener + { + + public void documentEventOccured( DocumentEvent i_documentEvent ) + { + if ( i_documentEvent.EventName.equals( "OnUnload" ) ) + { + synchronized( m_observedCloseEvents ) + { + m_observedCloseEvents.add( CloseEventType.OnUnload ); + } + } + } + + public void disposing(EventObject eo) + { + // not interested in + } + } + + private class CloseListener implements XCloseListener + { + + public void queryClosing(EventObject eo, boolean bln) throws CloseVetoException + { + // not interested in + } + + public void notifyClosing(EventObject eo) + { + synchronized( m_observedCloseEvents ) + { + m_observedCloseEvents.add( CloseEventType.NotifyClosing ); + } + } + + public void disposing(EventObject eo) + { + // not interested in + } + } + + private class DocDisposeListener implements XEventListener + { + public void disposing(EventObject eo) + { + synchronized( m_observedCloseEvents ) + { + m_observedCloseEvents.add( CloseEventType.Disposing ); + } + synchronized ( m_document ) + { + m_document.notifyAll(); + } + } + } + + private class DocCloser extends Thread + { + @Override + public void run() + { + try + { + final XCloseable docCloseable = UnoRuntime.queryInterface(XCloseable.class, m_document.getDocument()); + docCloseable.close(true); + } + catch (CloseVetoException ex) + { + Logger.getLogger(DocumentEvents.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + private enum CloseEventType + { + OnUnload, + NotifyClosing, + Disposing + } + + private OfficeDocument m_document = null; + final private ArrayList< CloseEventType > m_observedCloseEvents = new ArrayList<DocumentEvents.CloseEventType>(); +} diff --git a/sfx2/qa/complex/sfx2/DocumentMetadataAccess.java b/sfx2/qa/complex/sfx2/DocumentMetadataAccess.java new file mode 100644 index 000000000..5697bba07 --- /dev/null +++ b/sfx2/qa/complex/sfx2/DocumentMetadataAccess.java @@ -0,0 +1,1228 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import helper.StreamSimulator; + +import lib.TestParameters; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openoffice.test.OfficeConnection; + +import com.sun.star.beans.Pair; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.StringPair; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XEnumeration; +import com.sun.star.container.XEnumerationAccess; +import com.sun.star.frame.XStorable; +import com.sun.star.io.XInputStream; +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.rdf.BlankNode; +import com.sun.star.rdf.FileFormat; +import com.sun.star.rdf.Literal; +import com.sun.star.rdf.Statement; +import com.sun.star.rdf.URI; +import com.sun.star.rdf.URIs; +import com.sun.star.rdf.XBlankNode; +import com.sun.star.rdf.XDocumentMetadataAccess; +import com.sun.star.rdf.XDocumentRepository; +import com.sun.star.rdf.XLiteral; +import com.sun.star.rdf.XMetadatable; +import com.sun.star.rdf.XNamedGraph; +import com.sun.star.rdf.XNode; +import com.sun.star.rdf.XQuerySelectResult; +import com.sun.star.rdf.XRepository; +import com.sun.star.rdf.XRepositorySupplier; +import com.sun.star.rdf.XURI; +import com.sun.star.text.XText; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.XCloseable; +import complex.sfx2.tools.TestDocument; + +/** + * Test case for interface com.sun.star.rdf.XDocumentMetadataAccess + * Currently, this service is implemented in + * sfx2/source/doc/DocumentMetadataAccess.cxx + * + * Actually, this is not a service, so we need to create a document and + * go from there... + * + */ +public class DocumentMetadataAccess +{ + XMultiServiceFactory xMSF; + XComponentContext xContext; + String tempDir; + + String nsRDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + String nsRDFS = "http://www.w3.org/2000/01/rdf-schema#"; + String nsPkg="http://docs.oasis-open.org/opendocument/meta/package/common#"; + String nsODF ="http://docs.oasis-open.org/opendocument/meta/package/odf#"; + + XURI foo; + XURI bar; + XURI baz; + + static XURI rdf_type; + static XURI rdfs_label; + static XURI pkg_Document; + static XURI pkg_hasPart; + static XURI pkg_MetadataFile; + static XURI odf_ContentFile; + static XURI odf_StylesFile; + static XURI odf_Element; + static XBlankNode blank1; + static XBlankNode blank2; + static XBlankNode blank3; + static XBlankNode blank4; + static String manifestPath = "manifest.rdf"; + static String contentPath = "content.xml"; + static String stylesPath = "styles.xml"; + static String fooPath = "foo.rdf"; + static String fooBarPath = "meta/foo/bar.rdf"; + + XRepository xRep; + XRepositorySupplier xRS; + XDocumentMetadataAccess xDMA; + + /** + * The test parameters + */ + private static TestParameters param = null; + + @Before public void before() throws Exception + { + xMSF = getMSF(); + param = new TestParameters(); + param.put("ServiceFactory", xMSF); // important for param.getMSF() + + assertNotNull("could not create MultiServiceFactory.", xMSF); + XPropertySet xPropertySet = UnoRuntime.queryInterface(XPropertySet.class, xMSF); + Object defaultCtx = xPropertySet.getPropertyValue("DefaultContext"); + xContext = UnoRuntime.queryInterface(XComponentContext.class, defaultCtx); + assertNotNull("could not get component context.", xContext); + + tempDir = util.utils.getOfficeTemp/*Dir*/(xMSF); + System.out.println("tempdir: " + tempDir); + + foo = URI.create(xContext, "uri:foo"); + assertNotNull("foo", foo); + bar = URI.create(xContext, "uri:bar"); + assertNotNull("bar", bar); + baz = URI.create(xContext, "uri:baz"); + assertNotNull("baz", baz); + + blank1 = BlankNode.create(xContext, "_:1"); + assertNotNull("blank1", blank1); + blank2 = BlankNode.create(xContext, "_:2"); + assertNotNull("blank2", blank2); + blank3 = BlankNode.create(xContext, "_:3"); + assertNotNull("blank3", blank3); + blank4 = BlankNode.create(xContext, "_:4"); + assertNotNull("blank4", blank4); + rdf_type = URI.createKnown(xContext, URIs.RDF_TYPE); + assertNotNull("rdf_type", rdf_type); + rdfs_label = URI.createKnown(xContext, URIs.RDFS_LABEL); + assertNotNull("rdfs_label", rdfs_label); + pkg_Document = URI.createKnown(xContext, URIs.PKG_DOCUMENT); + assertNotNull("pkg_Document", pkg_Document); + pkg_hasPart = URI.createKnown(xContext, URIs.PKG_HASPART); + assertNotNull("pkg_hasPart", pkg_hasPart); + pkg_MetadataFile = URI.createKnown(xContext, URIs.PKG_METADATAFILE); + assertNotNull("pkg_MetadataFile", pkg_MetadataFile); + odf_ContentFile = URI.createKnown(xContext, URIs.ODF_CONTENTFILE); + assertNotNull("odf_ContentFile", odf_ContentFile); + odf_StylesFile = URI.createKnown(xContext, URIs.ODF_STYLESFILE); + assertNotNull("odf_StylesFile", odf_StylesFile); + odf_Element = URI.createKnown(xContext, URIs.ODF_ELEMENT); + assertNotNull("odf_Element", odf_Element); + } + + @After public void after() + { + xRep = null; + xRS = null; + xDMA = null; + } + + @Test public void check() throws Exception + { + XComponent xComp = null; + XComponent xComp2 = null; + try { + XEnumeration xStmtsEnum; + XNamedGraph xManifest; + + System.out.println("Creating document with Repository..."); + + // we cannot create a XDMA directly, we must create + // a document and get it from there :( + // create document + PropertyValue[] loadProps = new PropertyValue[1]; + loadProps[0] = new PropertyValue(); + loadProps[0].Name = "Hidden"; + loadProps[0].Value = true; + xComp = util.DesktopTools.openNewDoc(xMSF, "swriter", loadProps); + XTextDocument xText = UnoRuntime.queryInterface(XTextDocument.class, xComp); + + XRepositorySupplier xRepoSupplier = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp); + assertNotNull("xRS null", xRepoSupplier); + XDocumentMetadataAccess xDocMDAccess = UnoRuntime.queryInterface(XDocumentMetadataAccess.class, xRepoSupplier); + assertNotNull("xDMA null", xDocMDAccess); + xRep = xRepoSupplier.getRDFRepository(); + assertNotNull("xRep null", xRep); + + System.out.println("...done"); + + System.out.println("Checking that new repository is initialized..."); + + XURI xBaseURI = xDocMDAccess; + String baseURI = xBaseURI.getStringValue(); + assertNotNull("new: baseURI", xBaseURI ); + assertTrue("new: baseURI", !xBaseURI.getStringValue().equals("")); + + assertTrue("new: # graphs", 1 == xRep.getGraphNames().length); + XURI manifest = URI.createNS(xContext, xBaseURI.getStringValue(), + manifestPath); + xManifest = xRep.getGraph(manifest); + assertTrue("new: manifest graph", null != xManifest); + + Statement[] manifestStmts = getManifestStmts(xBaseURI); + xStmtsEnum = xRep.getStatements(null, null, null); + assertTrue("new: manifest graph", eq(xStmtsEnum, manifestStmts)); + + System.out.println("...done"); + + System.out.println("Checking some invalid args..."); + + String content = "behold, for I am the content."; + new TestRange(content); + + try { + xDocMDAccess.getElementByURI(null); + fail("getElementByURI: null allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.getMetadataGraphsWithType(null); + fail("getMetadataGraphsWithType: null URI allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("", new XURI[0]); + fail("addMetadataFile: empty filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("/foo", new XURI[0]); + fail("addMetadataFile: absolute filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("fo\"o", new XURI[0]); + fail("addMetadataFile: invalid filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("../foo", new XURI[0]); + fail("addMetadataFile: filename with .. allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("foo/../../bar", new XURI[0]); + fail("addMetadataFile: filename with nest .. allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("foo/././bar", new XURI[0]); + fail("addMetadataFile: filename with nest . allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("content.xml", new XURI[0]); + fail("addMetadataFile: content.xml allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("styles.xml", new XURI[0]); + fail("addMetadataFile: styles.xml allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("meta.xml", new XURI[0]); + fail("addMetadataFile: meta.xml allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addMetadataFile("settings.xml", new XURI[0]); + fail("addMetadataFile: settings.xml allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, null, "foo", + foo, new XURI[0]); + fail("importMetadataFile: null stream allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + + final String sEmptyRDF = TestDocument.getUrl("empty.rdf"); + try { + XInputStream xFooIn = new StreamSimulator(sEmptyRDF, true, param); + xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, xFooIn, "", + foo, new XURI[0]); + fail("importMetadataFile: empty filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + XInputStream xFooIn = + new StreamSimulator(sEmptyRDF, true, param); + xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, xFooIn, "meta.xml", + foo, new XURI[0]); + fail("importMetadataFile: meta.xml filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + XInputStream xFooIn = + new StreamSimulator(sEmptyRDF, true, param); + xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, + xFooIn, "foo", null, new XURI[0]); + fail("importMetadataFile: null base URI allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + XInputStream xFooIn = + new StreamSimulator(sEmptyRDF, true, param); + xDocMDAccess.importMetadataFile(FileFormat.RDF_XML, + xFooIn, "foo", rdf_type, new XURI[0]); + fail("importMetadataFile: non-absolute base URI allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.removeMetadataFile(null); + fail("removeMetadataFile: null URI allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addContentOrStylesFile(""); + fail("addContentOrStylesFile: empty filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addContentOrStylesFile("/content.xml"); + fail("addContentOrStylesFile: absolute filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.addContentOrStylesFile("foo.rdf"); + fail("addContentOrStylesFile: invalid filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.removeContentOrStylesFile(""); + fail("removeContentOrStylesFile: empty filename allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.loadMetadataFromStorage(null, foo, null); + fail("loadMetadataFromStorage: null storage allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.storeMetadataToStorage(null/*, base*/); + fail("storeMetadataToStorage: null storage allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.loadMetadataFromMedium(new PropertyValue[0]); + fail("loadMetadataFromMedium: empty medium allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + try { + xDocMDAccess.storeMetadataToMedium(new PropertyValue[0]); + fail("storeMetadataToMedium: empty medium allowed"); + } catch (IllegalArgumentException e) { + // ignore + } + + System.out.println("...done"); + + System.out.println("Checking file addition/removal..."); + + xDocMDAccess.removeContentOrStylesFile(contentPath); + xStmtsEnum = xManifest.getStatements(null, null, null); + assertTrue("removeContentOrStylesFile (content)", + eq(xStmtsEnum, new Statement[] { + manifestStmts[0], manifestStmts[2], manifestStmts[4] + })); + + xDocMDAccess.addContentOrStylesFile(contentPath); + xStmtsEnum = xManifest.getStatements(null, null, null); + assertTrue("addContentOrStylesFile (content)", + eq(xStmtsEnum, manifestStmts)); + + xDocMDAccess.removeContentOrStylesFile(stylesPath); + xStmtsEnum = xManifest.getStatements(null, null, null); + assertTrue("removeContentOrStylesFile (styles)", + eq(xStmtsEnum, new Statement[] { + manifestStmts[0], manifestStmts[1], manifestStmts[3] + })); + + xDocMDAccess.addContentOrStylesFile(stylesPath); + xStmtsEnum = xManifest.getStatements(null, null, null); + assertTrue("addContentOrStylesFile (styles)", + eq(xStmtsEnum, manifestStmts)); + + XURI xFoo = URI.createNS(xContext, xBaseURI.getStringValue(), + fooPath); + Statement xM_BaseHaspartFoo = + new Statement(xBaseURI, pkg_hasPart, xFoo, manifest); + Statement xM_FooTypeMetadata = + new Statement(xFoo, rdf_type, pkg_MetadataFile, manifest); + Statement xM_FooTypeBar = + new Statement(xFoo, rdf_type, bar, manifest); + xDocMDAccess.addMetadataFile(fooPath, new XURI[] { bar }); + xStmtsEnum = xManifest.getStatements(null, null, null); + assertTrue("addMetadataFile", + eq(xStmtsEnum, merge(manifestStmts, new Statement[] { + xM_BaseHaspartFoo, xM_FooTypeMetadata, xM_FooTypeBar + }))); + + XURI[] graphsBar = xDocMDAccess.getMetadataGraphsWithType(bar); + assertTrue("getMetadataGraphsWithType", + graphsBar.length == 1 && eq(graphsBar[0], xFoo)); + + + xDocMDAccess.removeMetadataFile(xFoo); + xStmtsEnum = xManifest.getStatements(null, null, null); + assertTrue("removeMetadataFile", + eq(xStmtsEnum, manifestStmts)); + + System.out.println("...done"); + + System.out.println("Checking mapping..."); + + XEnumerationAccess xTextEnum = UnoRuntime.queryInterface(XEnumerationAccess.class, xText.getText()); + Object o = xTextEnum.createEnumeration().nextElement(); + XMetadatable xMeta1 = UnoRuntime.queryInterface(XMetadatable.class, o); + + XMetadatable xMeta; + xMeta = xDocMDAccess.getElementByURI(xMeta1); + assertTrue("getElementByURI: null", null != xMeta); + String XmlId = xMeta.getMetadataReference().Second; + String XmlId1 = xMeta1.getMetadataReference().Second; + assertTrue("getElementByURI: no xml id", !XmlId.equals("")); + assertTrue("getElementByURI: different xml id", XmlId.equals(XmlId1)); + + System.out.println("...done"); + + System.out.println("Checking storing and loading..."); + + XURI xFoobar = URI.createNS(xContext, xBaseURI.getStringValue(), + fooBarPath); + Statement[] metadataStmts = getMetadataFileStmts(xBaseURI, + fooBarPath); + xDocMDAccess.addMetadataFile(fooBarPath, new XURI[0]); + xStmtsEnum = xRep.getStatements(null, null, null); + assertTrue("addMetadataFile", + eq(xStmtsEnum, merge(manifestStmts, metadataStmts ))); + + Statement xFoobar_FooBarFoo = + new Statement(foo, bar, foo, xFoobar); + xRep.getGraph(xFoobar).addStatement(foo, bar, foo); + xStmtsEnum = xRep.getStatements(null, null, null); + assertTrue("addStatement", + eq(xStmtsEnum, merge(manifestStmts, merge(metadataStmts, + new Statement[] { xFoobar_FooBarFoo })))); + + PropertyValue noMDNoContentFile = new PropertyValue(); + noMDNoContentFile.Name = "URL"; + noMDNoContentFile.Value = TestDocument.getUrl("CUSTOM.odt"); + PropertyValue noMDFile = new PropertyValue(); + noMDFile.Name = "URL"; + noMDFile.Value = TestDocument.getUrl("TEST.odt"); + PropertyValue file = new PropertyValue(); + file.Name = "URL"; + file.Value = tempDir + "TESTDMA.odt"; + PropertyValue mimetype = new PropertyValue(); + mimetype.Name = "MediaType"; + mimetype.Value = "application/vnd.oasis.opendocument.text"; + PropertyValue[] argsEmptyNoContent = { mimetype, noMDNoContentFile}; + PropertyValue[] argsEmpty = { mimetype, noMDFile }; + PropertyValue[] args = { mimetype, file }; + + xStmtsEnum = xRep.getStatements(null, null, null); + XURI[] graphs = xRep.getGraphNames(); + + xDocMDAccess.storeMetadataToMedium(args); + + // this should re-init + xDocMDAccess.loadMetadataFromMedium(argsEmptyNoContent); + xRep = xRepoSupplier.getRDFRepository(); + assertTrue("xRep null", null != xRep); + assertTrue("baseURI still tdoc?", + !baseURI.equals(xDocMDAccess.getStringValue())); + Statement[] manifestStmts2 = getManifestStmts(xDocMDAccess); + xStmtsEnum = xRep.getStatements(null, null, null); + // there is no content or styles file in here, so we have just + // the package stmt + assertTrue("loadMetadataFromMedium (no metadata, no content)", + eq(xStmtsEnum, new Statement[] { manifestStmts2[0] })); + + // this should re-init + xDocMDAccess.loadMetadataFromMedium(argsEmpty); + xRep = xRepoSupplier.getRDFRepository(); + assertTrue("xRep null", null != xRep); + assertTrue("baseURI still tdoc?", + !baseURI.equals(xDocMDAccess.getStringValue())); + Statement[] manifestStmts3 = getManifestStmts(xDocMDAccess); + + xStmtsEnum = xRep.getStatements(null, null, null); + assertTrue("loadMetadataFromMedium (no metadata)", + eq(xStmtsEnum, manifestStmts3)); + + xDocMDAccess.loadMetadataFromMedium(args); + xRep = xRepoSupplier.getRDFRepository(); + assertTrue("xRep null", null != xRep); + Statement[] manifestStmts4 = getManifestStmts(xDocMDAccess); + Statement[] metadataStmts4 = getMetadataFileStmts(xDocMDAccess, + fooBarPath); + + xStmtsEnum = xRep.getStatements(null, null, null); + assertTrue("some graph(s) not reloaded", + graphs.length == xRep.getGraphNames().length); + + XURI xFoobar4 = URI.createNS(xContext, xDocMDAccess.getStringValue(), + fooBarPath); + Statement xFoobar_FooBarFoo4 = + new Statement(foo, bar, foo, xFoobar4); + assertTrue("loadMetadataFromMedium (re-load)", + eq(xStmtsEnum, merge(manifestStmts4, merge(metadataStmts4, + new Statement[] { xFoobar_FooBarFoo4 })))); + + System.out.println("...done"); + + System.out.println("Checking storing and loading via model..."); + + String f = tempDir + "TESTPARA.odt"; + + XStorable xStor = UnoRuntime.queryInterface(XStorable.class, xRepoSupplier); + + xStor.storeToURL(f, new PropertyValue[0]); + + xComp2 = util.DesktopTools.loadDoc(xMSF, f, loadProps); + + XDocumentMetadataAccess xDMA2 = UnoRuntime.queryInterface(XDocumentMetadataAccess.class, xComp2); + assertTrue("xDMA2 null", null != xDMA2); + + XRepositorySupplier xRS2 = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp2); + assertTrue("xRS2 null", null != xRS2); + + XRepository xRep2 = xRS2.getRDFRepository(); + assertTrue("xRep2 null", null != xRep2); + + Statement[] manifestStmts5 = getManifestStmts(xDMA2); + Statement[] metadataStmts5 = getMetadataFileStmts(xDMA2, + fooBarPath); + XURI xFoobar5 = URI.createNS(xContext, xDMA2.getStringValue(), + fooBarPath); + Statement xFoobar_FooBarFoo5 = + new Statement(foo, bar, foo, xFoobar5); + xStmtsEnum = xRep.getStatements(null, null, null); + XEnumeration xStmtsEnum2 = xRep2.getStatements(null, null, null); + assertTrue("load: repository differs", + eq(xStmtsEnum2, merge(manifestStmts5, merge(metadataStmts5, + new Statement[] { xFoobar_FooBarFoo5 })))); + + System.out.println("...done"); + + } finally { + close(xComp); + close(xComp2); + } + } + + @Test public void checkRDFa() throws Exception + { + XComponent xComp = null; + try { + final String file = TestDocument.getUrl("TESTRDFA.odt"); + xComp = loadRDFa(file); + if (xComp != null) + { + final String sNewFile = tempDir + "TESTRDFA.odt"; + storeRDFa(xComp, sNewFile); + close(xComp); + + xComp = loadRDFa(sNewFile); + } + } finally { + close(xComp); + } + } + + @Test + public void checkTdf123293() throws Exception + { + XComponent xComp = null; + try { + xComp = util.DesktopTools.loadDocUsingStream(xMSF, TestDocument.getPath("TESTRDFA.odt")); + + // Metadata was discarded when loading from stream, make sure it's there now + XRepositorySupplier xRepoSupplier = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp); + assertNotNull("No metadata loaded", xRepoSupplier); + } finally { + close(xComp); + } + } + + private void storeRDFa(XComponent xComp, String file) throws com.sun.star.io.IOException + { + System.out.println("Storing test document..."); + + XStorable xStor = UnoRuntime.queryInterface(XStorable.class, xComp); + + xStor.storeToURL(file, new PropertyValue[0]); + + System.out.println("...done"); + } + + private XComponent loadRDFa(String file) throws Exception + { + XComponent xComp = null; + + System.out.println("Loading test document..."); + + PropertyValue[] loadProps = new PropertyValue[1]; + loadProps[0] = new PropertyValue(); + loadProps[0].Name = "Hidden"; + loadProps[0].Value = true; + + + + xComp = util.DesktopTools.loadDoc(xMSF, file, loadProps); + + XRepositorySupplier xRepoSupplier = UnoRuntime.queryInterface(XRepositorySupplier.class, xComp); + assertTrue("xRS null", null != xRepoSupplier); + + XDocumentRepository xDocRepository = UnoRuntime.queryInterface(XDocumentRepository.class, xRepoSupplier.getRDFRepository()); + assertTrue("xRep null", null != xDocRepository); + + XTextDocument xTextDoc = UnoRuntime.queryInterface(XTextDocument.class, xComp); + + XText xText = xTextDoc.getText(); + + XEnumerationAccess xEA = UnoRuntime.queryInterface(XEnumerationAccess.class, xText); + XEnumeration xEnum = xEA.createEnumeration(); + + System.out.println("...done"); + + System.out.println("Checking RDFa in loaded test document..."); + + XMetadatable xPara; + Pair<Statement[], Boolean> result; + + Statement x_FooBarLit1 = new Statement(foo, bar, mkLit("1"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 1", + !result.Second && + eq(result.First, new Statement[] { + x_FooBarLit1 + })); + + Statement x_FooBarLit2 = new Statement(foo, bar, mkLit("2"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 2", + !result.Second && + eq(result.First, new Statement[] { + x_FooBarLit2 + })); + + Statement x_BlankBarLit3 = + new Statement(blank1, bar, mkLit("3"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 3", + !result.Second && + eq(result.First, new Statement[] { + x_BlankBarLit3 + })); + XBlankNode b3 = UnoRuntime.queryInterface(XBlankNode.class, result.First[0].Subject); + + Statement x_BlankBarLit4 = + new Statement(blank2, bar, mkLit("4"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 4", + !result.Second && + eq(result.First, new Statement[] { + x_BlankBarLit4 + })); + XBlankNode b4 = UnoRuntime.queryInterface(XBlankNode.class, result.First[0].Subject); + + Statement x_BlankBarLit5 = + new Statement(blank1, bar, mkLit("5"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 5", + !result.Second && + eq(result.First, new Statement[] { + x_BlankBarLit5 + })); + XBlankNode b5 = UnoRuntime.queryInterface(XBlankNode.class, result.First[0].Subject); + + assertTrue("RDFa: 3 != 4", + !b3.getStringValue().equals(b4.getStringValue())); + assertTrue("RDFa: 3 == 5", + b3.getStringValue().equals(b5.getStringValue())); + + Statement x_FooBarLit6 = new Statement(foo, bar, mkLit("6"), null); + Statement x_FooBazLit6 = new Statement(foo, baz, mkLit("6"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 6", + !result.Second && + eq(result.First, new Statement[] { + x_FooBarLit6, x_FooBazLit6 + })); + + Statement x_FooBarLit7 = new Statement(foo, bar, mkLit("7"), null); + Statement x_FooBazLit7 = new Statement(foo, baz, mkLit("7"), null); + Statement x_FooFooLit7 = new Statement(foo, foo, mkLit("7"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 7", + !result.Second && + eq(result.First, new Statement[] { + x_FooBarLit7, x_FooBazLit7, x_FooFooLit7 + })); + + XNode lit = mkLit("a fooish bar"); + XNode lit_type= mkLit("a fooish bar", bar); + Statement x_FooBarLit = new Statement(foo, bar, lit, null); + Statement x_FooBarLittype = new Statement(foo, bar, lit_type, null); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 8", + result.Second && + eq(result.First, new Statement[] { + x_FooBarLit + })); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 9", + result.Second && + eq(result.First, new Statement[] { + x_FooBarLit + })); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 10", + result.Second && + eq(result.First, new Statement[] { + x_FooBarLittype + })); + + Statement x_FooBarLit11 + = new Statement(foo, bar, mkLit("11", bar), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 11", + !result.Second && + eq(result.First, new Statement[] { + x_FooBarLit11 + })); + + XURI xFile = URI.createNS(xContext, file, "/" + contentPath); + Statement x_FileBarLit12 = + new Statement(xFile, bar, mkLit("12"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 12", + !result.Second && + eq(result.First, new Statement[] { + x_FileBarLit12 + })); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 13", + result.Second && + eq(result.First, new Statement[] { + x_FooBarLit + })); + + new Statement(foo, rdfs_label, mkLit("14"), null); + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 14", + result.Second && + eq(result.First, new Statement[] { + /* x_FooLabelLit14 */ x_FooBarLit + })); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 15", eq(result.First, new Statement[] { } )); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 16", eq(result.First, new Statement[] { } )); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 17", eq(result.First, new Statement[] { } )); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 18", eq(result.First, new Statement[] { } )); + + xPara = UnoRuntime.queryInterface(XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 19", eq(result.First, new Statement[] { } )); + + xPara = UnoRuntime.queryInterface( + XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 20", eq(result.First, new Statement[] { } )); + + xPara = UnoRuntime.queryInterface( + XMetadatable.class, xEnum.nextElement()); + result = xDocRepository.getStatementRDFa(xPara); + assertTrue("RDFa: 21", eq(result.First, new Statement[] { } )); + + System.out.println("...done"); + + return xComp; + } + + +// utilities ------------------------------------------------------------- + + static void close(XComponent i_comp) + { + try { + XCloseable xClos = UnoRuntime.queryInterface(XCloseable.class, i_comp); + if (xClos != null) + { + xClos.close(true); + } + } catch (Exception e) { + } + } + + XLiteral mkLit(String i_content) + { + return Literal.create(xContext, i_content); + } + + XLiteral mkLit(String i_content, XURI i_uri) + { + return Literal.createWithType(xContext, i_content, i_uri); + } + + static Statement[] merge(Statement[] i_A1, Statement[] i_A2) + { + // bah, java sucks... + Statement[] ret = new Statement[i_A1.length + i_A2.length]; + for (int i = 0; i < i_A1.length; ++i) { + ret[i] = i_A1[i]; + } + for (int i = 0; i < i_A2.length; ++i) { + ret[i+i_A1.length] = i_A2[i]; + } + return ret; + } + + public static String toS(XNode n) { + if (null == n) + { + return "< null >"; + } + return n.getStringValue(); + } + + static boolean isBlank(XNode i_node) + { + XBlankNode blank = UnoRuntime.queryInterface(XBlankNode.class, i_node); + return blank != null; + } + + + static Statement[] toSeq(XEnumeration i_Enum) throws Exception + { + java.util.Collection<Statement> c = new java.util.ArrayList<Statement>(); + while (i_Enum.hasMoreElements()) { + Statement s = (Statement) i_Enum.nextElement(); + c.add(s); + } + // java sucks + Object[] arr = c.toArray(); + Statement[] ret = new Statement[arr.length]; + for (int i = 0; i < arr.length; ++i) { + ret[i] = (Statement) arr[i]; + } + return ret; + } + + static XNode[][] toSeqs(XEnumeration i_Enum) throws Exception + { + java.util.Collection<XNode[]> c = new java.util.ArrayList<XNode[]>(); + while (i_Enum.hasMoreElements()) { + XNode[] s = (XNode[]) i_Enum.nextElement(); + c.add(s); + } + Object[] arr = c.toArray(); + XNode[][] ret = new XNode[arr.length][]; + for (int i = 0; i < arr.length; ++i) { + ret[i] = (XNode[]) arr[i]; + } + return ret; + } + + private static class BindingComp implements java.util.Comparator<XNode[]> + { + public int compare(XNode[] left, XNode[] right) + { + if (left.length != right.length) + { + throw new RuntimeException(); + } + for (int i = 0; i < left.length; ++i) { + int eq = (left[i].getStringValue().compareTo( + right[i].getStringValue())); + if (eq != 0) + { + return eq; + } + } + return 0; + } + } + + private static class StmtComp implements java.util.Comparator<Statement> + { + public int compare(Statement left, Statement right) + { + int eq; + if ((eq = cmp(left.Graph, right.Graph )) != 0) return eq; + if ((eq = cmp(left.Subject, right.Subject )) != 0) return eq; + if ((eq = cmp(left.Predicate, right.Predicate)) != 0) return eq; + if ((eq = cmp(left.Object, right.Object )) != 0) return eq; + return 0; + } + + private int cmp(XNode i_Left, XNode i_Right) + { + if (isBlank(i_Left)) { + return isBlank(i_Right) ? 0 : 1; + } else { + if (isBlank(i_Right)) { + return -1; + } else { + return toS(i_Left).compareTo(toS(i_Right)); + } + } + } + } + + static boolean eq(Statement i_Left, Statement i_Right) + { + XURI lG = i_Left.Graph; + XURI rG = i_Right.Graph; + if (!eq(lG, rG)) { + System.out.println("Graphs differ: " + toS(lG) + " != " + toS(rG)); + return false; + } + if (!eq(i_Left.Subject, i_Right.Subject)) { + System.out.println("Subjects differ: " + + i_Left.Subject.getStringValue() + " != " + + i_Right.Subject.getStringValue()); + return false; + } + if (!eq(i_Left.Predicate, i_Right.Predicate)) { + System.out.println("Predicates differ: " + + i_Left.Predicate.getStringValue() + " != " + + i_Right.Predicate.getStringValue()); + return false; + } + if (!eq(i_Left.Object, i_Right.Object)) { + System.out.println("Objects differ: " + + i_Left.Object.getStringValue() + " != " + + i_Right.Object.getStringValue()); + return false; + } + return true; + } + + static boolean eq(Statement[] i_Result, Statement[] i_Expected) + { + if (i_Result.length != i_Expected.length) { + System.out.println("eq: different lengths: " + i_Result.length + " " + + i_Expected.length); + return false; + } + Statement[] expected = i_Expected.clone(); + java.util.Arrays.sort(i_Result, new StmtComp()); + java.util.Arrays.sort(expected, new StmtComp()); + for (int i = 0; i < expected.length; ++i) + { + // This is better for debug! + final Statement a = i_Result[i]; + final Statement b = expected[i]; + final boolean cond = eq(a, b); + if (!cond) return false; + } + return true; + } + + static boolean eq(XEnumeration i_Enum, Statement[] i_Expected) + throws Exception + { + Statement[] current = toSeq(i_Enum); + return eq(current, i_Expected); + } + + static boolean eq(XNode i_Left, XNode i_Right) + { + if (i_Left == null) { + return (i_Right == null); + } else { + return (i_Right != null) && + (i_Left.getStringValue().equals(i_Right.getStringValue()) + // FIXME: hack: blank nodes considered equal + || (isBlank(i_Left) && isBlank(i_Right))); + } + } + + static boolean eq(XQuerySelectResult i_Result, + String[] i_Vars, XNode[][] i_Bindings) throws Exception + { + String[] vars = i_Result.getBindingNames(); + XEnumeration iter = i_Result; + XNode[][] bindings = toSeqs(iter); + if (vars.length != i_Vars.length) { + System.out.println("var lengths differ"); + return false; + } + if (bindings.length != i_Bindings.length) { + System.out.println("binding lengths differ: " + i_Bindings.length + + " vs " + bindings.length ); + return false; + } + java.util.Arrays.sort(bindings, new BindingComp()); + java.util.Arrays.sort(i_Bindings, new BindingComp()); + for (int i = 0; i < i_Bindings.length; ++i) { + if (i_Bindings[i].length != i_Vars.length) { + System.out.println("TEST ERROR!"); + throw new Exception(); + } + if (bindings[i].length != i_Vars.length) { + System.out.println("binding length and var length differ"); + return false; + } + for (int j = 0; j < i_Vars.length; ++j) { + if (!eq(bindings[i][j], i_Bindings[i][j])) { + System.out.println("bindings differ: " + + toS(bindings[i][j]) + " != " + toS(i_Bindings[i][j])); + return false; + } + } + } + for (int i = 0; i < i_Vars.length; ++i) { + if (!vars[i].equals(i_Vars[i])) { + System.out.println("variable names differ: " + + vars[i] + " != " + i_Vars[i]); + return false; + } + } + return true; + } + + static boolean eq(StringPair i_Left, StringPair i_Right) + { + return ((i_Left.First).equals(i_Right.First)) && + ((i_Left.Second).equals(i_Right.Second)); + } + + static String mkNamespace(String i_prefix, String i_namespace) + { + return "PREFIX " + i_prefix + ": <" + i_namespace + ">\n"; + } + + static String mkNss() + { + String namespaces = mkNamespace("rdf", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + namespaces += mkNamespace("pkg", + "http://docs.oasis-open.org/opendocument/meta/package/common#"); + namespaces += mkNamespace("odf", + "http://docs.oasis-open.org/opendocument/meta/package/odf#"); + return namespaces; + } + + Statement[] getManifestStmts(XURI xBaseURI) throws Exception + { + XURI xManifest = URI.createNS(xContext, xBaseURI.getStringValue(), + manifestPath); + XURI xContent = URI.createNS(xContext, xBaseURI.getStringValue(), + contentPath); + XURI xStyles = URI.createNS(xContext, xBaseURI.getStringValue(), + stylesPath); + Statement xM_BaseTypeDoc = + new Statement(xBaseURI, rdf_type, pkg_Document, xManifest); + Statement xM_BaseHaspartContent = + new Statement(xBaseURI, pkg_hasPart, xContent, xManifest); + Statement xM_BaseHaspartStyles = + new Statement(xBaseURI, pkg_hasPart, xStyles, xManifest); + Statement xM_ContentTypeContent = + new Statement(xContent, rdf_type, odf_ContentFile, xManifest); + Statement xM_StylesTypeStyles = + new Statement(xStyles, rdf_type, odf_StylesFile, xManifest); + return new Statement[] { + xM_BaseTypeDoc, xM_BaseHaspartContent, xM_BaseHaspartStyles, + xM_ContentTypeContent, xM_StylesTypeStyles + }; + } + + Statement[] getMetadataFileStmts(XURI xBaseURI, String Path) + throws Exception + { + XURI xManifest = URI.createNS(xContext, xBaseURI.getStringValue(), + manifestPath); + XURI xGraph = URI.createNS(xContext, xBaseURI.getStringValue(), Path); + Statement xM_BaseHaspartGraph = + new Statement(xBaseURI, pkg_hasPart, xGraph, xManifest); + Statement xM_GraphTypeMetadata = + new Statement(xGraph, rdf_type, pkg_MetadataFile, xManifest); + return new Statement[] { xM_BaseHaspartGraph, xM_GraphTypeMetadata }; + } + + class TestRange implements XTextRange, XMetadatable, XServiceInfo + { + String m_Stream; + String m_XmlId; + String m_Text; + TestRange(String i_Str) { m_Text = i_Str; } + + public String getStringValue() { return ""; } + public String getNamespace() { return ""; } + public String getLocalName() { return ""; } + + public StringPair getMetadataReference() + { + return new StringPair(m_Stream, m_XmlId); + } + public void setMetadataReference(StringPair i_Ref) + throws IllegalArgumentException + { + m_Stream = i_Ref.First; + m_XmlId = i_Ref.Second; + } + public void ensureMetadataReference() + { + m_Stream = "content.xml"; + m_XmlId = "42"; + } + + public String getImplementationName() { return null; } + public String[] getSupportedServiceNames() { return null; } + public boolean supportsService(String i_Svc) + { + return i_Svc.equals("com.sun.star.text.Paragraph"); + } + + public XText getText() { return null; } + public XTextRange getStart() { return null; } + public XTextRange getEnd() { return null; } + public String getString() { return m_Text; } + public void setString(String i_Str) { m_Text = i_Str; } + } + + + + private XMultiServiceFactory getMSF() + { + return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager()); + } + + // setup and close connections + @BeforeClass public static void setUpConnection() throws Exception { + System.out.println( "------------------------------------------------------------" ); + System.out.println( "starting class: " + DocumentMetadataAccess.class.getName() ); + System.out.println( "------------------------------------------------------------" ); + connection.setUp(); + } + + @AfterClass public static void tearDownConnection() + throws InterruptedException, com.sun.star.uno.Exception + { + System.out.println( "------------------------------------------------------------" ); + System.out.println( "finishing class: " + DocumentMetadataAccess.class.getName() ); + System.out.println( "------------------------------------------------------------" ); + connection.tearDown(); + } + + private static final OfficeConnection connection = new OfficeConnection(); + +} + diff --git a/sfx2/qa/complex/sfx2/DocumentProperties.java b/sfx2/qa/complex/sfx2/DocumentProperties.java new file mode 100644 index 000000000..c5f84b1b7 --- /dev/null +++ b/sfx2/qa/complex/sfx2/DocumentProperties.java @@ -0,0 +1,516 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2; + + +import complex.sfx2.tools.TestDocument; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.lang.XInitialization; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.Locale; +import com.sun.star.lang.EventObject; +import com.sun.star.util.Date; +import com.sun.star.util.DateTime; +import com.sun.star.util.Time; +import com.sun.star.util.Duration; +import com.sun.star.util.XModifyListener; +import com.sun.star.util.XModifyBroadcaster; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.NamedValue; +import com.sun.star.beans.PropertyAttribute; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.IllegalTypeException; + +import com.sun.star.document.XDocumentProperties; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openoffice.test.OfficeConnection; +import static org.junit.Assert.*; + +/** + * Test case for the service com.sun.star.document.DocumentProperties. + * Currently, this service is implemented in + * sfx2/source/doc/SfxDocumentMetaData.cxx. + * + */ +public class DocumentProperties +{ + @After public void cleanup() { + // nothing to do + } + + // for testing modifications + private class Listener implements XModifyListener { + private boolean m_Called; + + Listener() { + m_Called = false; + } + + private boolean reset() { + boolean oldCalled = m_Called; + m_Called = false; + return oldCalled; + } + + public void modified(EventObject e) { + m_Called = true; + } + + public void disposing(EventObject e) { + } + } + + @Test public void check() throws Exception + { + XMultiServiceFactory xMSF = getMSF(); + assertNotNull("could not create MultiServiceFactory.", xMSF); + XPropertySet xPropertySet = UnoRuntime.queryInterface(XPropertySet.class, xMSF); + Object defaultCtx = xPropertySet.getPropertyValue("DefaultContext"); + XComponentContext xContext = UnoRuntime.queryInterface(XComponentContext.class, defaultCtx); + assertNotNull("could not get component context.", xContext); + + // TODO: Path to temp + String temp = util.utils.getOfficeTemp/*Dir*/(xMSF); + System.out.println("tempdir: " + temp); + + PropertyValue[] noArgs = { }; + PropertyValue mimetype = new PropertyValue(); + mimetype.Name = "MediaType"; + mimetype.Value = "application/vnd.oasis.opendocument.text"; + PropertyValue[] mimeArgs = { mimetype }; + PropertyValue cfile = new PropertyValue(); + cfile.Name = "URL"; + cfile.Value = temp + "EMPTY.odt"; + PropertyValue[] mimeEmptyArgs = { mimetype, cfile }; + + System.out.println("Creating service DocumentProperties..."); + + Object oDP = + xMSF.createInstance("com.sun.star.document.DocumentProperties"); + XDocumentProperties xDP = UnoRuntime.queryInterface(XDocumentProperties.class, oDP); + + System.out.println("...done"); + + + System.out.println("Checking initialize ..."); + + XDocumentProperties xDP2 = UnoRuntime.queryInterface(XDocumentProperties.class, xMSF.createInstance("com.sun.star.document.DocumentProperties")); + XInitialization xInit = UnoRuntime.queryInterface(XInitialization.class, xDP2); + xInit.initialize(new Object[] { }); + + System.out.println("...done"); + + System.out.println("Checking storing default-initialized meta data ..."); + + xDP2.storeToMedium("", mimeEmptyArgs); + + System.out.println("...done"); + + System.out.println("Checking loading default-initialized meta data ..."); + + xDP2.loadFromMedium("", mimeEmptyArgs); + assertEquals("Author", "", xDP2.getAuthor()); + + System.out.println("...done"); + + System.out.println("Checking loading from test document..."); + + String file = TestDocument.getUrl("TEST.odt"); + xDP.loadFromMedium(file, noArgs); +/* XInputStream xStream = + new StreamSimulator("./testdocuments/TEST.odt", true, param); + Object oSF = + xMSF.createInstance("com.sun.star.embed.StorageFactory"); + XSingleServiceFactory xSF = (XSingleServiceFactory) + UnoRuntime.queryInterface(XSingleServiceFactory.class, oSF); + Object oStor = xSF.createInstanceWithArguments( + new Object[] { xStream }); + XStorage xStor = (XStorage) UnoRuntime.queryInterface( + XStorage.class, oStor); + xDP.loadFromStorage(xStor);*/ + + System.out.println("...done"); + + System.out.println("Checking meta-data import..."); + + assertEquals("Author", "Karl-Heinz Mustermann", xDP.getAuthor()); + assertEquals( + "Generator", + "StarOffice/8$Solaris_x86 OpenOffice.org_project/680m232$Build-9227", + xDP.getGenerator()); + assertEquals("CreationDate", 2007, xDP.getCreationDate().Year); + assertEquals("Title", "Urgent Memo", xDP.getTitle()); + assertEquals("Subject", "Wichtige Mitteilung", xDP.getSubject()); + assertEquals( + "Description", + "Modern internal company memorandum in full-blocked style", + xDP.getDescription()); + assertEquals( + "ModifiedBy", "Karl-Heinz Mustermann", xDP.getModifiedBy()); + assertEquals( + "ModificationDate", 10, xDP.getModificationDate().Month); + assertEquals( + "PrintedBy", "Karl-Heinz Mustermann", xDP.getPrintedBy()); + assertEquals("PrintDate", 29, xDP.getPrintDate().Day); + assertEquals("TemplateName", "Modern Memo", xDP.getTemplateName()); + assertTrue("TemplateURL", + xDP.getTemplateURL().endsWith("memmodern.ott")); + assertEquals("TemplateDate", 17, xDP.getTemplateDate().Hours); + assertTrue( + "AutoloadURL", xDP.getAutoloadURL().endsWith("/TEST.odt")); + assertEquals("AutoloadSecs", 0, xDP.getAutoloadSecs()); + assertEquals("DefaultTarget", "_blank", xDP.getDefaultTarget()); + assertEquals("EditingCycles", 3, xDP.getEditingCycles()); + assertEquals("EditingDuration", 320, xDP.getEditingDuration()); + + String[] kws = xDP.getKeywords(); + assertTrue("Keywords", fromArray(kws).containsAll( + fromArray(new Object[] { "Asien", "Memo", "Reis" }))); + + NamedValue[] ds = xDP.getDocumentStatistics(); + assertTrue("DocumentStatistics:WordCount", containsNV(ds, + new NamedValue("WordCount", Integer.valueOf(23)))); + assertTrue("DocumentStatistics:PageCount", containsNV(ds, + new NamedValue("PageCount", Integer.valueOf(1)))); + + XPropertyContainer udpc = xDP.getUserDefinedProperties(); + XPropertySet udps = UnoRuntime.queryInterface( XPropertySet.class, udpc ); + assertEquals( + "UserDefined 1", "Dies ist ein wichtiger Hinweis", + udps.getPropertyValue("Hinweis")); + assertEquals( + "UserDefined 2", "Kann Spuren von N\u00FCssen enthalten", + udps.getPropertyValue("Warnung")); + + System.out.println("...done"); + + System.out.println("Checking meta-data updates..."); + + String str; + DateTime dt = new DateTime(); + Locale l = new Locale(); + int i; + + str = "me"; + xDP.setAuthor(str); + assertEquals("setAuthor", str, xDP.getAuthor()); + str = "the computa"; + xDP.setGenerator(str); + assertEquals("setGenerator", str, xDP.getGenerator()); + dt.Year = 2038; + dt.Month = 1; + dt.Day = 1; + xDP.setCreationDate(dt); + assertEquals( + "setCreationDate", dt.Year, xDP.getCreationDate().Year); + str = "El t'itulo"; + xDP.setTitle(str); + assertEquals("setTitle", str, xDP.getTitle()); + str = "Ein verkommenes Subjekt"; + xDP.setSubject(str); + assertEquals("setSubject", str, xDP.getSubject()); + str = "Este descripci'on no es importante"; + xDP.setDescription(str); + assertEquals("setDescription", str, xDP.getDescription()); + l.Language = "en"; + l.Country = "GB"; + xDP.setLanguage(l); + Locale l2 = xDP.getLanguage(); + assertEquals("setLanguage Lang", l.Language, l2.Language); + assertEquals("setLanguage Cty", l.Country, l2.Country); + str = "myself"; + xDP.setModifiedBy(str); + assertEquals("setModifiedBy", str, xDP.getModifiedBy()); + dt.Year = 2042; + xDP.setModificationDate(dt); + assertEquals( + "setModificationDate", dt.Year, xDP.getModificationDate().Year); + str = "i did not do it"; + xDP.setPrintedBy(str); + assertEquals("setPrintedBy", str, xDP.getPrintedBy()); + dt.Year = 2024; + xDP.setPrintDate(dt); + assertEquals("setPrintDate", dt.Year, xDP.getPrintDate().Year); + str = "blah"; + xDP.setTemplateName(str); + assertEquals("setTemplateName", str, xDP.getTemplateName()); + str = "gopher://some-hole-in-the-ground/"; + xDP.setTemplateURL(str); + assertEquals("setTemplateURL", str, xDP.getTemplateURL()); + dt.Year = 2043; + xDP.setTemplateDate(dt); + assertEquals( + "setTemplateDate", dt.Year, xDP.getTemplateDate().Year); + str = "http://nowhere/"; + xDP.setAutoloadURL(str); + assertEquals("setAutoloadURL", str, xDP.getAutoloadURL()); + i = 3661; // this might not work (due to conversion via double...) + xDP.setAutoloadSecs(i); + assertEquals("setAutoloadSecs", i, xDP.getAutoloadSecs()); + str = "_blank"; + xDP.setDefaultTarget(str); + assertEquals("setDefaultTarget", str, xDP.getDefaultTarget()); + i = 42; + xDP.setEditingCycles((short) i); + assertEquals("setEditingCycles", i, xDP.getEditingCycles()); + i = 84; + xDP.setEditingDuration(i); + assertEquals("setEditingDuration", i, xDP.getEditingDuration()); + str = ""; + + String[] kws2 = new String[] { + "keywordly", "keywordlike", "keywordalicious" }; + xDP.setKeywords(kws2); + kws = xDP.getKeywords(); + assertTrue("setKeywords", fromArray(kws).containsAll(fromArray(kws2))); + + NamedValue[] ds2 = new NamedValue[] { + new NamedValue("SyllableCount", Integer.valueOf(9)), + new NamedValue("FrameCount", Integer.valueOf(2)), + new NamedValue("SentenceCount", Integer.valueOf(7)) }; + xDP.setDocumentStatistics(ds2); + ds = xDP.getDocumentStatistics(); + assertTrue("setDocumentStatistics:SyllableCount", containsNV(ds, + new NamedValue("SyllableCount", Integer.valueOf(9)))); + assertTrue("setDocumentStatistics:FrameCount", containsNV(ds, + new NamedValue("FrameCount", Integer.valueOf(2)))); + assertTrue("setDocumentStatistics:SentenceCount", containsNV(ds, + new NamedValue("SentenceCount", Integer.valueOf(7)))); + + System.out.println("...done"); + + System.out.println("Checking user-defined meta-data updates..."); + + // actually, this tests the PropertyBag service + // but maybe the DocumentProperties service will be implemented + // differently some day... + boolean b = true; + double d = 3.1415; + // note that Time is only supported for backward compatibility! + Time t = new Time(); + t.Hours = 1; + t.Minutes = 16; + Date date = new Date(); + date.Year = 2071; + date.Month = 2; + date.Day = 3; + dt.Year = 2065; + Duration dur = new Duration(); + dur.Negative = true; + dur.Years = 1001; + dur.Months = 999; + dur.Days = 888; + dur.Hours = 777; + dur.Minutes = 666; + dur.Seconds = 555; + dur.NanoSeconds = 444444444; + + udpc.addProperty("Frobnicate", PropertyAttribute.REMOVABLE, b); + udpc.addProperty("FrobDuration", PropertyAttribute.REMOVABLE, dur); + udpc.addProperty("FrobDuration2", PropertyAttribute.REMOVABLE, t); + udpc.addProperty("FrobEndDate", PropertyAttribute.REMOVABLE, date); + udpc.addProperty("FrobStartTime", PropertyAttribute.REMOVABLE, dt); + udpc.addProperty("Pi", PropertyAttribute.REMOVABLE, new Double(d)); + udpc.addProperty("Foo", PropertyAttribute.REMOVABLE, "bar"); + udpc.addProperty("Removed", PropertyAttribute.REMOVABLE, "bar"); + // #i94175#: empty property name is valid ODF 1.1 + udpc.addProperty("", PropertyAttribute.REMOVABLE, "eeeeek"); + try { + udpc.removeProperty("Info 1"); + udpc.removeProperty("Removed"); + } catch (UnknownPropertyException e) { + fail("removeProperty failed"); + } + + try { + udpc.addProperty("Forbidden", PropertyAttribute.REMOVABLE, + new String[] { "foo", "bar" }); + fail("inserting value of non-supported type did not fail"); + } catch (IllegalTypeException e) { + // ignore + } + + assertEquals( + "UserDefined bool", b, udps.getPropertyValue("Frobnicate")); + assertTrue("UserDefined duration", eqDuration(dur, (Duration) + udps.getPropertyValue("FrobDuration"))); + assertTrue("UserDefined time", eqTime(t, (Time) + udps.getPropertyValue("FrobDuration2"))); + assertTrue("UserDefined date", eqDate(date, (Date) + udps.getPropertyValue("FrobEndDate"))); + assertTrue("UserDefined datetime", eqDateTime(dt, (DateTime) + udps.getPropertyValue("FrobStartTime"))); + assertEquals("UserDefined float", d, udps.getPropertyValue("Pi")); + assertEquals( + "UserDefined string", "bar", udps.getPropertyValue("Foo")); + assertEquals( + "UserDefined empty name", "eeeeek", udps.getPropertyValue("")); + + try { + udps.getPropertyValue("Removed"); + fail("UserDefined remove didn't"); + } catch (UnknownPropertyException e) { + // ok + } + + System.out.println("...done"); + + System.out.println("Checking storing meta-data to file..."); + + xDP.storeToMedium(temp + "TEST.odt", mimeArgs); + + System.out.println("...done"); + + System.out.println("Checking loading meta-data from stored file..."); + + xDP.loadFromMedium(temp + "TEST.odt", noArgs); + + System.out.println("...done"); + + System.out.println("Checking user-defined meta-data from stored file..."); + + udpc = xDP.getUserDefinedProperties(); + udps = UnoRuntime.queryInterface( XPropertySet.class, udpc ); + + assertEquals( + "UserDefined bool", b, udps.getPropertyValue("Frobnicate")); + assertTrue("UserDefined duration", eqDuration(dur, (Duration) + udps.getPropertyValue("FrobDuration"))); + // this is now a Duration! + Duration t_dur = new Duration(false, (short)0, (short)0, (short)0, + t.Hours, t.Minutes, t.Seconds, + t.NanoSeconds); + assertTrue("UserDefined time", eqDuration(t_dur, (Duration) + udps.getPropertyValue("FrobDuration2"))); + assertTrue("UserDefined date", eqDate(date, (Date) + udps.getPropertyValue("FrobEndDate"))); + assertTrue("UserDefined datetime", eqDateTime(dt, (DateTime) + udps.getPropertyValue("FrobStartTime"))); + assertEquals("UserDefined float", d, udps.getPropertyValue("Pi")); + assertEquals( + "UserDefined string", "bar", udps.getPropertyValue("Foo")); + + try { + udps.getPropertyValue("Removed"); + fail("UserDefined remove didn't"); + } catch (UnknownPropertyException e) { + // ok + } + + System.out.println("...done"); + + System.out.println("Checking notification listener interface..."); + + Listener listener = new Listener(); + XModifyBroadcaster xMB = UnoRuntime.queryInterface( XModifyBroadcaster.class, xDP ); + xMB.addModifyListener(listener); + xDP.setAuthor("not me"); + assertTrue("Listener Author", listener.reset()); + udpc.addProperty("Listener", PropertyAttribute.REMOVABLE, "foo"); + assertTrue("Listener UserDefined Add", listener.reset()); + udps.setPropertyValue("Listener", "bar"); + assertTrue("Listener UserDefined Set", listener.reset()); + udpc.removeProperty("Listener"); + assertTrue("Listener UserDefined Remove", listener.reset()); + xMB.removeModifyListener(listener); + udpc.addProperty("Listener2", PropertyAttribute.REMOVABLE, "foo"); + assertTrue("Removed Listener UserDefined Add", !listener.reset()); + + System.out.println("...done"); + } + + // grrr... + boolean eqDateTime(DateTime a, DateTime b) { + return a.Year == b.Year && a.Month == b.Month && a.Day == b.Day + && a.Hours == b.Hours && a.Minutes == b.Minutes + && a.Seconds == b.Seconds + && a.NanoSeconds == b.NanoSeconds; + } + + boolean eqDate(Date a, Date b) { + return a.Year == b.Year && a.Month == b.Month && a.Day == b.Day; + } + + boolean eqTime(Time a, Time b) { + return a.Hours == b.Hours && a.Minutes == b.Minutes + && a.Seconds == b.Seconds + && a.NanoSeconds == b.NanoSeconds; + } + + boolean eqDuration(Duration a, Duration b) { + return a.Years == b.Years && a.Months == b.Months && a.Days == b.Days + && a.Hours == b.Hours && a.Minutes == b.Minutes + && a.Seconds == b.Seconds + && a.NanoSeconds == b.NanoSeconds + && a.Negative == b.Negative; + } + + java.util.Collection<Object> fromArray(Object[] os) { + java.util.Collection<Object> ret = new java.util.HashSet<Object>(); + for (int i = 0; i < os.length; ++i) { + ret.add(os[i]); + } + return ret; + } + + // bah, structs do not have proper equals(), and uno.Type is not comparable + public static boolean containsNV (NamedValue[] nvs, NamedValue nv) { + for (int i = 0; i < nvs.length; ++i) { + if (nvs[i].Name.equals(nv.Name) && nvs[i].Value.equals(nv.Value)) { + return true; + } + } + return false; + } + + private XMultiServiceFactory getMSF() + { + final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface( XMultiServiceFactory.class, connection.getComponentContext().getServiceManager() ); + return xMSF1; + } + + // setup and close connections + @BeforeClass public static void setUpConnection() throws Exception { + System.out.println( "------------------------------------------------------------" ); + System.out.println( "starting class: " + DocumentProperties.class.getName() ); + System.out.println( "------------------------------------------------------------" ); + connection.setUp(); + } + + @AfterClass public static void tearDownConnection() + throws InterruptedException, com.sun.star.uno.Exception + { + System.out.println( "------------------------------------------------------------" ); + System.out.println( "finishing class: " + DocumentProperties.class.getName() ); + System.out.println( "------------------------------------------------------------" ); + connection.tearDown(); + } + + private static final OfficeConnection connection = new OfficeConnection(); + +} + diff --git a/sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java b/sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java new file mode 100644 index 000000000..7c42cdf1f --- /dev/null +++ b/sfx2/qa/complex/sfx2/GlobalEventBroadcaster.java @@ -0,0 +1,249 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.sfx2; + +import com.sun.star.awt.XWindow; +import com.sun.star.document.XEventBroadcaster; +import com.sun.star.document.XEventListener; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.sheet.XSpreadsheetDocument; +import com.sun.star.text.XTextDocument; +import com.sun.star.uno.UnoRuntime; +import complex.sfx2.tools.WriterHelper; + +import java.util.ArrayList; + +import util.UITools; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openoffice.test.OfficeConnection; +import static org.junit.Assert.*; + + +/** + * This testcase checks the GlobalEventBroadcaster + * it will add an XEventListener and verify the Events + * raised when opening/changing and closing Office Documents + */ +public class GlobalEventBroadcaster { + XMultiServiceFactory m_xMSF = null; + XEventBroadcaster m_xEventBroadcaster = null; + ArrayList<String> notifyEvents = new ArrayList<String>(); + // XTextDocument xTextDoc; + XSpreadsheetDocument xSheetDoc; + XEventListener m_xEventListener = new EventListenerImpl(); + + @Before public void initialize() { + m_xMSF = getMSF(); + System.out.println("check whether there is a valid MultiServiceFactory"); + + assertNotNull("## Couldn't get MultiServiceFactory make sure your Office is started", m_xMSF); + + System.out.println("... done"); + + System.out.println( + "Create an instance of com.sun.star.frame.GlobalEventBroadcaster"); + + Object GlobalEventBroadcaster = null; + + try { + GlobalEventBroadcaster = m_xMSF.createInstance( + "com.sun.star.frame.GlobalEventBroadcaster"); + } catch (com.sun.star.uno.Exception e) { + fail("## Exception while creating instance"); + } + + System.out.println("... done"); + + System.out.println("check whether the created instance is valid"); + + assertNotNull("couldn't create service", GlobalEventBroadcaster); + + System.out.println("... done"); + + System.out.println( + "try to query the XEventBroadcaster from the gained Object"); + m_xEventBroadcaster = UnoRuntime.queryInterface(XEventBroadcaster.class, GlobalEventBroadcaster); + + if (util.utils.isVoid(m_xEventBroadcaster)) { + fail("couldn't get XEventBroadcaster"); + } + + System.out.println("... done"); + + System.out.println("adding Listener"); + m_xEventBroadcaster.addEventListener(m_xEventListener); + System.out.println("... done"); + } + + @Test public void checkWriter() throws Exception { + System.out.println("-- Checking Writer --"); + + WriterHelper wHelper = new WriterHelper(m_xMSF); + String[] expected; + System.out.println("opening an empty writer doc"); + notifyEvents.clear(); + { + XTextDocument xTextDoc = wHelper.openEmptyDoc(); + util.utils.waitForEventIdle(m_xMSF); + expected = new String[] { "OnUnfocus", "OnCreate", "OnViewCreated", "OnFocus" }; + + assertTrue("Wrong events fired when opening empty doc", + proveExpectation(expected)); + System.out.println("... done"); + + System.out.println("changing the writer doc"); + notifyEvents.clear(); + xTextDoc.getText().setString("GlobalEventBroadcaster"); + util.utils.waitForEventIdle(m_xMSF); + expected = new String[] { "OnModifyChanged" }; + + assertTrue("Wrong events fired when changing doc", + proveExpectation(expected)); + System.out.println("... done"); + + System.out.println("closing the empty writer doc"); + notifyEvents.clear(); + wHelper.closeDoc(xTextDoc); + util.utils.waitForEventIdle(m_xMSF); + } + expected = new String[] { "OnUnfocus", "OnFocus", "OnViewClosed", "OnUnload" }; + + assertTrue("Wrong events fired when closing empty doc", + proveExpectation(expected)); + System.out.println("... done"); + + System.out.println("opening a writer doc via Window-New Window"); + notifyEvents.clear(); + { + XTextDocument xTextDoc = wHelper.openFromDialog(".uno:NewWindow", "", false); + + util.utils.waitForEventIdle(m_xMSF); + expected = new String[] { "OnUnfocus", "OnCreate", "OnViewCreated", "OnFocus", "OnUnfocus", "OnViewCreated", "OnFocus", }; + + assertTrue("Wrong events fired when opening a writer doc via Window-New Window", + proveExpectation(expected)); + System.out.println("... done"); + + System.out.println("closing the created writer doc"); + notifyEvents.clear(); + + wHelper.closeDoc(xTextDoc); + util.utils.waitForEventIdle(m_xMSF); + } + expected = new String[] { "OnViewClosed", "OnUnfocus", "OnFocus", "OnViewClosed", "OnUnload" }; + + assertTrue("Wrong events fired when closing Window-New Window", + proveExpectation(expected)); + + System.out.println("... done"); + // TODO: It seems not possible to close the document without interactive question + // there the follow test will not be execute + if (false) { + System.out.println("Opening document with label wizard"); + XTextDocument xTextDoc = wHelper.openFromDialog("private:factory/swriter?slot=21051", "", false); + util.utils.waitForEventIdle(m_xMSF); + XWindow xWindow = UnoRuntime.queryInterface(XWindow.class, wHelper.getToolkit().getActiveTopWindow()); + UITools ut = new UITools(xWindow); + notifyEvents.clear(); + System.out.println("pressing button 'New Document'"); + try{ + ut.clickButton ("New Document"); + } catch (Exception e) { + System.out.println("Couldn't press Button"); + } + System.out.println("... done"); + util.utils.waitForEventIdle(m_xMSF); + expected = new String[] { "OnViewClosed", "OnCreate", "OnFocus", "OnModifyChanged" }; + + assertTrue("Wrong events fired when starting labels wizard", + proveExpectation(expected)); + + System.out.println("Try to close document..."); + wHelper.closeDoc(xTextDoc); + util.utils.waitForEventIdle(m_xMSF); + wHelper.closeFromDialog(); + util.utils.waitForEventIdle(m_xMSF); + xTextDoc = null; + } + + System.out.println("-- Done Writer --"); + } + + @After public void cleanup() { + System.out.println("removing Listener"); + m_xEventBroadcaster.removeEventListener(m_xEventListener); + System.out.println("... done"); + } + + private boolean proveExpectation(String[] expected) { + boolean locRes = true; + boolean failure = false; + + System.out.println("Fired Events:"); + for (int k=0;k<notifyEvents.size();k++) { + System.out.println("\t- "+notifyEvents.get(k)); + } + + for (int i = 0; i < expected.length; i++) { + locRes = notifyEvents.contains(expected[i]); + + if (!locRes) { + System.out.println("The event " + expected[i] + " isn't fired"); + failure = true; + } + } + + return !failure; + } + + private class EventListenerImpl implements XEventListener { + public void disposing(com.sun.star.lang.EventObject eventObject) { + System.out.println("disposing: " + eventObject.Source.toString()); + } + + public void notifyEvent(com.sun.star.document.EventObject eventObject) { + notifyEvents.add(eventObject.EventName); + } + } + + private XMultiServiceFactory getMSF() + { + return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager()); + } + + // setup and close connections + @BeforeClass public static void setUpConnection() throws Exception { + System.out.println("setUpConnection()"); + connection.setUp(); + } + + @AfterClass public static void tearDownConnection() + throws InterruptedException, com.sun.star.uno.Exception + { + System.out.println("tearDownConnection() CheckGlobalEventBroadcaster_writer1"); + connection.tearDown(); + } + + private static final OfficeConnection connection = new OfficeConnection(); + +} diff --git a/sfx2/qa/complex/sfx2/UndoManager.java b/sfx2/qa/complex/sfx2/UndoManager.java new file mode 100644 index 000000000..12f13ada1 --- /dev/null +++ b/sfx2/qa/complex/sfx2/UndoManager.java @@ -0,0 +1,1459 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2; + +import com.sun.star.accessibility.XAccessible; +import com.sun.star.accessibility.XAccessibleAction; +import com.sun.star.awt.Point; +import com.sun.star.awt.Size; +import com.sun.star.awt.XControl; +import com.sun.star.awt.XControlModel; +import com.sun.star.awt.XToolkitExperimental; +import com.sun.star.beans.NamedValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.XChild; +import com.sun.star.container.XIndexContainer; +import com.sun.star.container.XNameContainer; +import com.sun.star.container.XNameReplace; +import com.sun.star.container.XSet; +import com.sun.star.document.EmptyUndoStackException; +import com.sun.star.document.UndoContextNotClosedException; +import com.sun.star.document.UndoFailedException; +import com.sun.star.document.UndoManagerEvent; +import com.sun.star.document.XEmbeddedScripts; +import com.sun.star.document.XEventsSupplier; +import com.sun.star.document.XUndoAction; +import com.sun.star.lang.EventObject; +import com.sun.star.lang.IndexOutOfBoundsException; +import com.sun.star.lang.XEventListener; + +import java.lang.reflect.InvocationTargetException; + +import org.openoffice.test.tools.OfficeDocument; + +import com.sun.star.document.XUndoManagerSupplier; +import com.sun.star.document.XUndoManager; +import com.sun.star.document.XUndoManagerListener; +import com.sun.star.drawing.XControlShape; +import com.sun.star.drawing.XDrawPage; +import com.sun.star.drawing.XDrawPageSupplier; +import com.sun.star.drawing.XShapes; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.lang.XTypeProvider; +import com.sun.star.script.ScriptEventDescriptor; +import com.sun.star.script.XEventAttacherManager; +import com.sun.star.script.XLibraryContainer; +import com.sun.star.task.XJob; +import com.sun.star.uno.Exception; +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.InvalidStateException; +import com.sun.star.util.NotLockedException; +import com.sun.star.view.XControlAccess; + +import complex.sfx2.undo.CalcDocumentTest; +import complex.sfx2.undo.ChartDocumentTest; +import complex.sfx2.undo.DocumentTest; +import complex.sfx2.undo.DrawDocumentTest; +import complex.sfx2.undo.ImpressDocumentTest; +import complex.sfx2.undo.WriterDocumentTest; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Stack; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.*; + +import org.openoffice.test.OfficeConnection; +import org.openoffice.test.tools.DocumentType; +import org.openoffice.test.tools.SpreadsheetDocument; + +/** + * Unit test for the UndoManager API + * + */ +public class UndoManager +{ + + @Before + public void beforeTest() throws com.sun.star.uno.Exception + { + m_currentTestCase = null; + m_currentDocument = null; + m_undoListener = null; + + // at our service factory, insert a new factory for our CallbackComponent + // this will allow the Basic code in our test documents to call back into this test case + // here, by just instantiating this service + final XSet globalFactory = UnoRuntime.queryInterface( XSet.class, getORB() ); + m_callbackFactory = new CallbackComponentFactory(); + globalFactory.insert( m_callbackFactory ); + } + + + @Test + public void checkWriterUndo() throws Exception + { + m_currentTestCase = new WriterDocumentTest( getORB() ); + impl_checkUndo(); + } + + +//FIXME fails fdo#35663 @Test + public void checkCalcUndo() throws java.lang.Exception + { + m_currentTestCase = new CalcDocumentTest( getORB() ); + impl_checkUndo(); + } + + + @Test + public void checkDrawUndo() throws Exception + { + m_currentTestCase = new DrawDocumentTest( getORB() ); + impl_checkUndo(); + } + + + @Test + public void checkImpressUndo() throws Exception + { + m_currentTestCase = new ImpressDocumentTest( getORB() ); + impl_checkUndo(); + } + + + @Test + public void checkChartUndo() throws Exception + { + m_currentTestCase = new ChartDocumentTest( getORB() ); + impl_checkUndo(); + } + + + @Test + public void checkBrokenScripts() throws com.sun.star.uno.Exception, InterruptedException + { + System.out.println( "testing: broken scripts" ); + + m_currentDocument = OfficeDocument.blankDocument( getORB(), DocumentType.CALC ); + m_undoListener = new UndoListener(); + getUndoManager().addUndoManagerListener( m_undoListener ); + + impl_setupBrokenBasicScript(); + final String scriptURI = "vnd.sun.star.script:default.callbacks.brokenScript?language=Basic&location=document"; + + + // scenario 1: Pressing a button which is bound to execute the script + // (This is one of the many cases where SfxObjectShell::CallXScript is invoked) + + // set up the button + final XPropertySet buttonModel = impl_setupButton(); + buttonModel.setPropertyValue( "Label", "exec broken script" ); + impl_assignScript( buttonModel, "XActionListener", "actionPerformed", + scriptURI ); + + // switch the doc's view to form alive mode (so the button will actually work) + m_currentDocument.getCurrentView().dispatch( ".uno:SwitchControlDesignMode" ); + XToolkitExperimental xToolkit = UnoRuntime.queryInterface( + XToolkitExperimental.class, + getORB().createInstance("com.sun.star.awt.Toolkit")); + xToolkit.processEventsToIdle(); + + // click the button + m_callbackCalled = false; + impl_clickButton( buttonModel ); + // the macro is executed asynchronously by the button, so wait at most 2 seconds for the callback to be + // triggered + impl_waitFor( m_callbackCondition, 20000 ); + // check the callback has actually been called + assertTrue( "clicking the test button did not work as expected - basic script not called", m_callbackCalled ); + + // again, since the script is executed asynchronously, we might arrive here while its execution + // is not completely finished. Give OOo another (at most) 2 seconds to finish it. + m_undoListener.waitForAllContextsClosed( 20000 ); + // assure that the Undo Context Depth of the doc is still "0": The Basic script entered such a + // context, and didn't close it (thus it is broken), but the application framework should have + // auto-closed the context after the macro finished. + assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() ); + + + // scenario 2: dispatching the script URL. Technically, this is equivalent to configuring the + // script into a menu or toolbar, and selecting the respective menu/toolbar item + m_callbackCalled = false; + m_currentDocument.getCurrentView().dispatch( scriptURI ); + assertTrue( "dispatching the Script URL did not work as expected - basic script not called", m_callbackCalled ); + // same as above: The script didn't close the context, but the OOo framework should have + assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() ); + + + // scenario 3: assigning the script to some document event, and triggering this event + final XEventsSupplier eventSupplier = UnoRuntime.queryInterface( XEventsSupplier.class, m_currentDocument.getDocument() ); + final XNameReplace events = UnoRuntime.queryInterface( XNameReplace.class, eventSupplier.getEvents() ); + final NamedValue[] scriptDescriptor = new NamedValue[] { + new NamedValue( "EventType", "Script" ), + new NamedValue( "Script", scriptURI ) + }; + events.replaceByName( "OnViewCreated", scriptDescriptor ); + + // note: this may be prevented from working by setting + // Office::Common::Security::Scripting::AllowedDocumentEventURLs + // (checked in SfxEvents_Impl::isScriptURLAllowed()) + m_callbackCalled = false; + m_currentDocument.getCurrentView().dispatch( ".uno:NewWindow" ); + assertTrue( "triggering an event did not work as expected - basic script not called", m_callbackCalled ); + // same as above: The script didn't close the context, but the OOo framework should have + assertEquals( "undo context was not auto-closed as expected", 0, m_undoListener.getCurrentUndoContextDepth() ); + + + // scenario 4: let the script enter an Undo context, but not close it, as usual. + // Additionally, let the script close the document - the OOo framework code which cares for + // auto-closing of Undo contexts should survive this, ideally ... + m_closeAfterCallback = true; + m_callbackCalled = false; + m_currentDocument.getCurrentView().dispatch( scriptURI ); + assertTrue( m_callbackCalled ); + assertTrue( "The Basic script should have closed the document.", m_undoListener.isDisposed() ); + m_currentDocument = null; + } + + + @Test + public void checkSerialization() throws com.sun.star.uno.Exception, InterruptedException + { + System.out.println( "testing: request serialization" ); + + m_currentDocument = OfficeDocument.blankDocument( getORB(), DocumentType.CALC ); + final XUndoManager undoManager = getUndoManager(); + + final int threadCount = 10; + final int actionsPerThread = 10; + final int actionCount = threadCount * actionsPerThread; + + // add some actions to the UndoManager, each knowing its position on the stack + final Object lock = new Object(); + final Integer actionsUndone[] = new Integer[] { 0 }; + for ( int i=actionCount; i>0; ) + undoManager.addUndoAction( new CountingUndoAction( --i, lock, actionsUndone ) ); + + // some concurrent threads which undo the actions + Thread[] threads = new Thread[threadCount]; + for ( int i=0; i<threadCount; ++i ) + { + threads[i] = new Thread() + { + @Override + public void run() + { + for ( int j=0; j<actionsPerThread; ++j ) + { + try { undoManager.undo(); } + catch ( final Exception e ) + { + fail( "Those dummy actions are not expected to fail." ); + return; + } + } + } + }; + } + + // start the threads + for ( int i=0; i<threadCount; ++i ) + threads[i].start(); + + // wait for them to be finished + for ( int i=0; i<threadCount; ++i ) + threads[i].join(); + + // ensure all actions have been undone + assertEquals( "not all actions have been undone", actionCount, actionsUndone[0].intValue() ); + } + + + @After + public void afterTest() + { + if ( m_currentTestCase != null ) + m_currentTestCase.closeDocument(); + else if ( m_currentDocument != null ) + m_currentDocument.close(); + m_currentTestCase = null; + m_currentDocument = null; + m_callbackFactory.dispose(); + } + + + /** + * @return returns the undo manager belonging to a given document + */ + private XUndoManager getUndoManager() + { + final XUndoManagerSupplier suppUndo = UnoRuntime.queryInterface( XUndoManagerSupplier.class, m_currentDocument.getDocument() ); + final XUndoManager undoManager = suppUndo.getUndoManager(); + assertTrue( UnoRuntime.areSame( undoManager.getParent(), m_currentDocument.getDocument() ) ); + return undoManager; + } + + + private void impl_waitFor( final Object i_condition, final int i_milliSeconds ) throws InterruptedException + { + synchronized( i_condition ) + { + i_condition.wait( i_milliSeconds ); + } + } + + + private void impl_setupBrokenBasicScript() + { + try + { + final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface( XEmbeddedScripts.class, m_currentDocument.getDocument() ); + final XLibraryContainer basicLibs = embeddedScripts.getBasicLibraries(); + final XNameContainer basicLib = basicLibs.createLibrary( "default" ); + + final String brokenScriptCode = + "Option Explicit\n" + + "\n" + + "Sub brokenScript\n" + + " Dim callback as Object\n" + + " ThisComponent.UndoManager.enterUndoContext( \"" + getCallbackUndoContextTitle() + "\" )\n" + + "\n" + + " callback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" + + " Dim emptyArgs() as new com.sun.star.beans.NamedValue\n" + + " Dim result as String\n" + + " result = callback.execute( emptyArgs() )\n" + + " If result = \"close\" Then\n" + + " ThisComponent.close( TRUE )\n" + + " End If\n" + + "End Sub\n" + + "\n"; + + basicLib.insertByName( "callbacks", brokenScriptCode ); + } + catch( com.sun.star.uno.Exception e ) + { + fail( "caught an exception while setting up the script: " + e.toString() ); + } + } + + + private XPropertySet impl_setupButton() throws com.sun.star.uno.Exception + { + // let the document create a shape + final XMultiServiceFactory docAsFactory = UnoRuntime.queryInterface( XMultiServiceFactory.class, + m_currentDocument.getDocument() ); + final XControlShape xShape = UnoRuntime.queryInterface( XControlShape.class, + docAsFactory.createInstance( "com.sun.star.drawing.ControlShape" ) ); + + // position and size of the shape + xShape.setSize( new Size( 28 * 100, 10 * 100 ) ); + xShape.setPosition( new Point( 10 * 100, 10 * 100 ) ); + + // create the form component (the model of a form control) + final String sQualifiedComponentName = "com.sun.star.form.component.CommandButton"; + final XControlModel controlModel = UnoRuntime.queryInterface( XControlModel.class, + getORB().createInstance( sQualifiedComponentName ) ); + + // knitt both + xShape.setControl( controlModel ); + + // add the shape to the shapes collection of the document + SpreadsheetDocument spreadsheetDoc = (SpreadsheetDocument)m_currentDocument; + final XDrawPageSupplier suppDrawPage = UnoRuntime.queryInterface( XDrawPageSupplier.class, + spreadsheetDoc.getSheet( 0 ) ); + final XDrawPage insertIntoPage = suppDrawPage.getDrawPage(); + + final XShapes sheetShapes = UnoRuntime.queryInterface( XShapes.class, insertIntoPage ); + sheetShapes.add( xShape ); + + return UnoRuntime.queryInterface( XPropertySet.class, controlModel ); + } + + + private void impl_assignScript( final XPropertySet i_controlModel, final String i_interfaceName, + final String i_interfaceMethod, final String i_scriptURI ) throws Exception + { + final XChild modelAsChild = UnoRuntime.queryInterface( XChild.class, i_controlModel ); + final XIndexContainer parentForm = UnoRuntime.queryInterface( XIndexContainer.class, modelAsChild.getParent() ); + + final XEventAttacherManager manager = UnoRuntime.queryInterface( XEventAttacherManager.class, parentForm ); + + int containerPosition = -1; + for ( int i = 0; i < parentForm.getCount(); ++i ) + { + final XPropertySet child = UnoRuntime.queryInterface( XPropertySet.class, parentForm.getByIndex( i ) ); + if ( UnoRuntime.areSame( child, i_controlModel ) ) + { + containerPosition = i; + break; + } + } + assertFalse( "could not find the given control model within its parent", containerPosition == -1 ); + manager.registerScriptEvent( containerPosition, new ScriptEventDescriptor( + i_interfaceName, + i_interfaceMethod, + "", + "Script", + i_scriptURI + ) ); + } + + + private void impl_clickButton( final XPropertySet i_buttonModel ) throws NoSuchElementException, IndexOutOfBoundsException + { + final XControlAccess controlAccess = UnoRuntime.queryInterface( XControlAccess.class, + m_currentDocument.getCurrentView().getController() ); + final XControl control = controlAccess.getControl( UnoRuntime.queryInterface( XControlModel.class, i_buttonModel ) ); + final XAccessible accessible = UnoRuntime.queryInterface( XAccessible.class, control ); + final XAccessibleAction controlActions = UnoRuntime.queryInterface( XAccessibleAction.class, accessible.getAccessibleContext() ); + for ( int i=0; i<controlActions.getAccessibleActionCount(); ++i ) + { + if (controlActions.getAccessibleActionDescription(i).equals("press")) + { + controlActions.doAccessibleAction(i); + return; + } + } + fail("did not find the accessible action named 'press'"); + } + + + private static class UndoListener implements XUndoManagerListener + { + public void undoActionAdded( UndoManagerEvent i_event ) + { + assertFalse( "|undoActionAdded| called after document was disposed", m_isDisposed ); + + ++m_undoActionsAdded; + m_mostRecentlyAddedAction = i_event.UndoActionTitle; + } + + public void actionUndone( UndoManagerEvent i_event ) + { + assertFalse( "|actionUndone| called after document was disposed", m_isDisposed ); + + ++m_undoCount; + m_mostRecentlyUndone = i_event.UndoActionTitle; + } + + public void actionRedone( UndoManagerEvent i_event ) + { + assertFalse( "|actionRedone| called after document was disposed", m_isDisposed ); + + ++m_redoCount; + } + + public void allActionsCleared( EventObject eo ) + { + assertFalse( "|allActionsCleared| called after document was disposed", m_isDisposed ); + + m_wasCleared = true; + } + + public void redoActionsCleared( EventObject eo ) + { + assertFalse( "|redoActionsCleared| called after document was disposed", m_isDisposed ); + + m_redoWasCleared = true; + } + + public void resetAll( EventObject i_event ) + { + assertFalse( "|resetAll| called after document was disposed", m_isDisposed ); + + m_managerWasReset = true; + m_activeUndoContexts.clear(); + } + + public void enteredContext( UndoManagerEvent i_event ) + { + assertFalse( "|enteredContext| called after document was disposed", m_isDisposed ); + + m_activeUndoContexts.push( i_event.UndoActionTitle ); + assertEquals( "different opinions on the context nesting level (after entering)", + m_activeUndoContexts.size(), i_event.UndoContextDepth ); + } + + public void enteredHiddenContext( UndoManagerEvent i_event ) + { + assertFalse( "|enteredHiddenContext| called after document was disposed", m_isDisposed ); + + m_activeUndoContexts.push( i_event.UndoActionTitle ); + assertEquals( "different opinions on the context nesting level (after entering hidden)", + m_activeUndoContexts.size(), i_event.UndoContextDepth ); + } + + public void leftContext( UndoManagerEvent i_event ) + { + assertFalse( "|leftContext| called after document was disposed", m_isDisposed ); + + assertEquals( "nested undo context descriptions do not match", m_activeUndoContexts.pop(), i_event.UndoActionTitle ); + assertEquals( "different opinions on the context nesting level (after leaving)", + m_activeUndoContexts.size(), i_event.UndoContextDepth ); + m_leftContext = true; + impl_notifyContextDepth(); + } + + public void leftHiddenContext( UndoManagerEvent i_event ) + { + assertFalse( "|leftHiddenContext| called after document was disposed", m_isDisposed ); + assertEquals( "|leftHiddenContext| is not expected to notify an action title", 0, i_event.UndoActionTitle.length() ); + + m_activeUndoContexts.pop(); + assertEquals( "different opinions on the context nesting level (after leaving)", + m_activeUndoContexts.size(), i_event.UndoContextDepth ); + m_leftHiddenContext = true; + impl_notifyContextDepth(); + } + + public void cancelledContext( UndoManagerEvent i_event ) + { + assertFalse( "|cancelledContext| called after document was disposed", m_isDisposed ); + assertEquals( "|cancelledContext| is not expected to notify an action title", 0, i_event.UndoActionTitle.length() ); + + m_activeUndoContexts.pop(); + assertEquals( "different opinions on the context nesting level (after cancelling)", + m_activeUndoContexts.size(), i_event.UndoContextDepth ); + m_cancelledContext = true; + impl_notifyContextDepth(); + } + + public void disposing( EventObject i_event ) + { + m_isDisposed = true; + } + + public void waitForAllContextsClosed( final int i_milliSeconds ) throws InterruptedException + { + synchronized ( m_allContextsClosedCondition ) + { + if ( m_activeUndoContexts.empty() ) + return; + m_allContextsClosedCondition.wait( i_milliSeconds ); + } + } + + private void impl_notifyContextDepth() + { + synchronized ( m_allContextsClosedCondition ) + { + if ( m_activeUndoContexts.empty() ) + { + m_allContextsClosedCondition.notifyAll(); + } + } + } + + private int getUndoActionsAdded() { return m_undoActionsAdded; } + private int getUndoActionCount() { return m_undoCount; } + private int getRedoActionCount() { return m_redoCount; } + private String getCurrentUndoContextTitle() { return m_activeUndoContexts.peek(); } + private String getMostRecentlyAddedActionTitle() { return m_mostRecentlyAddedAction; } + private String getMostRecentlyUndoneTitle() { return m_mostRecentlyUndone; } + private int getCurrentUndoContextDepth() { return m_activeUndoContexts.size(); } + private boolean isDisposed() { return m_isDisposed; } + private boolean wasContextLeft() { return m_leftContext; } + private boolean wasHiddenContextLeft() { return m_leftHiddenContext; } + private boolean hasContextBeenCancelled() { return m_cancelledContext; } + private boolean wereStacksCleared() { return m_wasCleared; } + private boolean wasRedoStackCleared() { return m_redoWasCleared; } + private boolean wasManagerReset() { return m_managerWasReset; } + + void reset() + { + m_undoActionsAdded = m_undoCount = m_redoCount = 0; + m_activeUndoContexts.clear(); + m_mostRecentlyAddedAction = m_mostRecentlyUndone = null; + // m_isDisposed is not cleared, intentionally + m_leftContext = m_leftHiddenContext = m_cancelledContext = m_wasCleared = m_redoWasCleared = m_managerWasReset = false; + } + + private int m_undoActionsAdded = 0; + private int m_undoCount = 0; + private int m_redoCount = 0; + private boolean m_isDisposed = false; + private boolean m_leftContext = false; + private boolean m_leftHiddenContext = false; + private boolean m_cancelledContext = false; + private boolean m_wasCleared = false; + private boolean m_redoWasCleared = false; + private boolean m_managerWasReset = false; + private Stack< String > + m_activeUndoContexts = new Stack<String>(); + private String m_mostRecentlyAddedAction = null; + private String m_mostRecentlyUndone = null; + private final Object m_allContextsClosedCondition = new Object(); + } + + + private void impl_checkUndo() throws Exception + { + System.out.println( "testing: " + m_currentTestCase.getDocumentDescription() ); + m_currentDocument = m_currentTestCase.getDocument(); + m_currentTestCase.initializeDocument(); + m_currentTestCase.verifyInitialDocumentState(); + + final XUndoManager undoManager = getUndoManager(); + undoManager.clear(); + assertFalse( "clearing the Undo manager should result in the impossibility to undo anything", undoManager.isUndoPossible() ); + assertFalse( "clearing the Undo manager should result in the impossibility to redo anything", undoManager.isRedoPossible() ); + + m_undoListener = new UndoListener(); + undoManager.addUndoManagerListener( m_undoListener ); + + impl_testSingleModification( undoManager ); + impl_testMultipleModifications( undoManager ); + impl_testCustomUndoActions( undoManager ); + impl_testLocking( undoManager ); + impl_testNestedContexts( undoManager ); + impl_testErrorHandling( undoManager ); + impl_testContextHandling( undoManager ); + impl_testStackHandling( undoManager ); + impl_testClearance( undoManager ); + impl_testHiddenContexts( undoManager ); + + // close the document, ensure the Undo manager listener gets notified + m_currentTestCase.closeDocument(); + m_currentTestCase = null; + m_currentDocument = null; + assertTrue( "document is closed, but the UndoManagerListener has not been notified of the disposal", m_undoListener.isDisposed() ); + } + + + private void impl_testSingleModification( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + m_currentTestCase.doSingleModification(); + m_currentTestCase.verifySingleModificationDocumentState(); + + // undo the modification, ensure the listener got the proper notifications + assertEquals( "We did not yet do an undo!", 0, m_undoListener.getUndoActionCount() ); + i_undoManager.undo(); + assertEquals( "A simple undo does not result in the proper Undo count.", + 1, m_undoListener.getUndoActionCount() ); + + // verify the document is in its initial state, again + m_currentTestCase.verifyInitialDocumentState(); + + // redo the modification, ensure the listener got the proper notifications + assertEquals( "not yet made a redo!", 0, m_undoListener.getRedoActionCount() ); + i_undoManager.redo(); + assertEquals( "made a redo, but got no notification of it!", 1, m_undoListener.getRedoActionCount() ); + // ensure the document is in the proper state, again + m_currentTestCase.verifySingleModificationDocumentState(); + + // now do an Undo via the UI (aka the dispatch API), and see if this works, and notifies the listener as + // expected + m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" ); + m_currentTestCase.verifyInitialDocumentState(); + assertEquals( "UI-Undo does not notify the listener", 2, m_undoListener.getUndoActionCount() ); + } + + + private void impl_testMultipleModifications( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + m_undoListener.reset(); + assertEquals( "unexpected initial undo context depth", 0, m_undoListener.getCurrentUndoContextDepth() ); + i_undoManager.enterUndoContext( "Batch Changes" ); + assertEquals( "unexpected undo context depth after entering a context", + 1, m_undoListener.getCurrentUndoContextDepth() ); + assertEquals( "entering an Undo context has not been notified properly", + "Batch Changes", m_undoListener.getCurrentUndoContextTitle() ); + + final int modifications = m_currentTestCase.doMultipleModifications(); + assertEquals( "unexpected number of undo actions while doing batch changes to the document", + modifications, m_undoListener.getUndoActionsAdded() ); + assertEquals( "seems the document operations touched the undo context depth", + 1, m_undoListener.getCurrentUndoContextDepth() ); + + i_undoManager.leaveUndoContext(); + assertEquals( "unexpected undo context depth after leaving the last context", + 0, m_undoListener.getCurrentUndoContextDepth() ); + assertEquals( "no Undo done, yet - still the listener has been notified of an Undo action", + 0, m_undoListener.getUndoActionCount() ); + + i_undoManager.undo(); + assertEquals( "Just did an undo - the listener should have been notified", 1, m_undoListener.getUndoActionCount() ); + m_currentTestCase.verifyInitialDocumentState(); + } + + + private void impl_testCustomUndoActions( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.clear(); + m_undoListener.reset(); + assertFalse( "undo stack not empty after clearing the undo manager", i_undoManager.isUndoPossible() ); + assertFalse( "redo stack not empty after clearing the undo manager", i_undoManager.isRedoPossible() ); + assertArrayEquals( ">0 descriptions for an empty undo stack?", + new String[0], i_undoManager.getAllUndoActionTitles() ); + assertArrayEquals( ">0 descriptions for an empty redo stack?", + new String[0], i_undoManager.getAllRedoActionTitles() ); + + // add two actions, one directly, one within a context + final CustomUndoAction action1 = new CustomUndoAction( "UndoAction1" ); + i_undoManager.addUndoAction( action1 ); + assertEquals( "Adding an undo action not observed by the listener", 1, m_undoListener.getUndoActionsAdded() ); + assertEquals( "Adding an undo action did not notify the proper title", + action1.getTitle(), m_undoListener.getMostRecentlyAddedActionTitle() ); + final String contextTitle = "Undo Context"; + i_undoManager.enterUndoContext( contextTitle ); + final CustomUndoAction action2 = new CustomUndoAction( "UndoAction2" ); + i_undoManager.addUndoAction( action2 ); + assertEquals( "Adding an undo action not observed by the listener", + 2, m_undoListener.getUndoActionsAdded() ); + assertEquals( "Adding an undo action did not notify the proper title", + action2.getTitle(), m_undoListener.getMostRecentlyAddedActionTitle() ); + i_undoManager.leaveUndoContext(); + + // see if the manager has proper descriptions + assertArrayEquals( "unexpected Redo descriptions after adding two actions", + new String[0], i_undoManager.getAllRedoActionTitles() ); + assertArrayEquals( "unexpected Undo descriptions after adding two actions", + new String[]{contextTitle, action1.getTitle()}, i_undoManager.getAllUndoActionTitles() ); + + // undo one action + i_undoManager.undo(); + assertEquals( "improper action title notified during programmatic Undo", + contextTitle, m_undoListener.getMostRecentlyUndoneTitle() ); + assertTrue( "nested custom undo action has not been undone as expected", action2.undoCalled() ); + assertFalse( "nested custom undo action has not been undone as expected", action1.undoCalled() ); + assertArrayEquals( "unexpected Redo descriptions after undoing a nested custom action", + new String[]{contextTitle}, i_undoManager.getAllRedoActionTitles() ); + assertArrayEquals( "unexpected Undo descriptions after undoing a nested custom action", + new String[]{action1.getTitle()}, i_undoManager.getAllUndoActionTitles() ); + + // undo the second action, via UI dispatches + m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" ); + assertEquals( "improper action title notified during UI Undo", action1.getTitle(), m_undoListener.getMostRecentlyUndoneTitle() ); + assertTrue( "nested custom undo action has not been undone as expected", action1.undoCalled() ); + assertArrayEquals( "unexpected Redo descriptions after undoing the second custom action", + new String[]{action1.getTitle(), contextTitle}, i_undoManager.getAllRedoActionTitles() ); + assertArrayEquals( "unexpected Undo descriptions after undoing the second custom action", + new String[0], i_undoManager.getAllUndoActionTitles() ); + + // check the actions are disposed when the stacks are cleared + i_undoManager.clear(); + assertTrue( action1.disposed() && action2.disposed() ); + } + + + private void impl_testLocking( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.reset(); + m_undoListener.reset(); + + // implicit Undo actions, triggered by changes to the document + assertFalse( "unexpected initial locking state", i_undoManager.isLocked() ); + i_undoManager.lock(); + assertTrue( "just locked the manager, why does it lie?", i_undoManager.isLocked() ); + m_currentTestCase.doSingleModification(); + assertEquals( "when the Undo manager is locked, no implicit additions should happen", + 0, m_undoListener.getUndoActionsAdded() ); + assertTrue( "Undo manager gets unlocked as a side effect of performing a simple operation", i_undoManager.isLocked() ); + i_undoManager.unlock(); + assertEquals( "unlock is not expected to add collected actions - they should be discarded", + 0, m_undoListener.getUndoActionsAdded() ); + assertFalse( "just unlocked the manager, why does it lie?", i_undoManager.isLocked() ); + + // explicit Undo actions + i_undoManager.lock(); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.unlock(); + assertEquals( "explicit Undo actions are expected to be ignored when the manager is locked", + 0, m_undoListener.getUndoActionsAdded() ); + + // Undo contexts while being locked + i_undoManager.lock(); + i_undoManager.enterUndoContext( "Dummy Context" ); + i_undoManager.enterHiddenUndoContext(); + assertEquals( "entering Undo contexts should be ignored when the manager is locked", 0, m_undoListener.getCurrentUndoContextDepth() ); + i_undoManager.leaveUndoContext(); + i_undoManager.leaveUndoContext(); + i_undoManager.unlock(); + + // |unlock| error handling + assertFalse( "internal error: manager should not be locked at this point in time", i_undoManager.isLocked() ); + boolean caughtExpected = false; + try { i_undoManager.unlock(); } catch ( final NotLockedException e ) { caughtExpected = true; } + assertTrue( "unlocking the manager when it is not locked should throw", caughtExpected ); + } + + + private void impl_testContextHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + + // part I: non-empty contexts + i_undoManager.reset(); + m_undoListener.reset(); + + // put one action on the undo and one on the redo stack, as precondition for the following tests + final XUndoAction undoAction1 = new CustomUndoAction( "Undo Action 1" ); + i_undoManager.addUndoAction( undoAction1 ); + final XUndoAction undoAction2 = new CustomUndoAction( "Undo Action 2" ); + i_undoManager.addUndoAction( undoAction2 ); + i_undoManager.undo(); + assertTrue( "precondition for context handling tests not met (1)", i_undoManager.isUndoPossible() ); + assertTrue( "precondition for context handling tests not met (2)", i_undoManager.isRedoPossible() ); + assertArrayEquals( new String[] { undoAction1.getTitle() }, i_undoManager.getAllUndoActionTitles() ); + assertArrayEquals( new String[] { undoAction2.getTitle() }, i_undoManager.getAllRedoActionTitles() ); + + final String[] expectedRedoActionComments = new String[] { undoAction2.getTitle() }; + assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() ); + + // enter a context + i_undoManager.enterUndoContext( "Undo Context" ); + // this should not (yet) touch the redo stack + assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() ); + assertEquals( "unexpected undo context depth after entering a context", 1, m_undoListener.getCurrentUndoContextDepth() ); + // add a single action + XUndoAction undoAction3 = new CustomUndoAction( "Undo Action 3" ); + i_undoManager.addUndoAction( undoAction3 ); + // still, the redo stack should be untouched - added at a lower level does not affect it at all + assertArrayEquals( expectedRedoActionComments, i_undoManager.getAllRedoActionTitles() ); + + // while the context is open, its title should already contribute to the stack, ... + assertEquals( "Undo Context", i_undoManager.getCurrentUndoActionTitle() ); + // ... getAllUndo/RedoActionTitles should operate on the top level, not on the level defined by the open + // context, ... + assertArrayEquals( new String[] { "Undo Context", undoAction1.getTitle() }, + i_undoManager.getAllUndoActionTitles() ); + // ... but Undo and Redo should be impossible as long as the context is open + assertFalse( i_undoManager.isUndoPossible() ); + assertFalse( i_undoManager.isRedoPossible() ); + + // leave the context, check the listener has been notified properly, and the notified context depth is correct + i_undoManager.leaveUndoContext(); + assertTrue( m_undoListener.wasContextLeft() ); + assertFalse( m_undoListener.wasHiddenContextLeft() ); + assertFalse( m_undoListener.hasContextBeenCancelled() ); + assertEquals( "unexpected undo context depth leaving a non-empty context", 0, m_undoListener.getCurrentUndoContextDepth() ); + // leaving a non-empty context should have cleared the redo stack + assertArrayEquals( new String[0], i_undoManager.getAllRedoActionTitles() ); + assertTrue( m_undoListener.wasRedoStackCleared() ); + + + // part II: empty contexts + i_undoManager.reset(); + m_undoListener.reset(); + + // enter a context, leave it immediately without adding an action to it + i_undoManager.enterUndoContext( "Undo Context" ); + i_undoManager.leaveUndoContext(); + assertFalse( m_undoListener.wasContextLeft() ); + assertFalse( m_undoListener.wasHiddenContextLeft() ); + assertTrue( m_undoListener.hasContextBeenCancelled() ); + assertFalse( "leaving an empty context should silently remove it, and not contribute to the stack", + i_undoManager.isUndoPossible() ); + } + + + private void impl_testNestedContexts( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.reset(); + m_undoListener.reset(); + i_undoManager.enterUndoContext( "context 1" ); + i_undoManager.enterUndoContext( "context 1.1" ); + final CustomUndoAction action1 = new CustomUndoAction( "action 1.1.1" ); + i_undoManager.addUndoAction( action1 ); + i_undoManager.enterUndoContext( "context 1.1.2" ); + final CustomUndoAction action2 = new CustomUndoAction( "action 1.1.2.1" ); + i_undoManager.addUndoAction( action2 ); + i_undoManager.leaveUndoContext(); + final CustomUndoAction action3 = new CustomUndoAction( "action 1.1.3" ); + i_undoManager.addUndoAction( action3 ); + i_undoManager.leaveUndoContext(); + i_undoManager.leaveUndoContext(); + final CustomUndoAction action4 = new CustomUndoAction( "action 1.2" ); + i_undoManager.addUndoAction( action4 ); + + i_undoManager.undo(); + assertEquals( "undoing a single action notifies a wrong title", action4.getTitle(), m_undoListener.getMostRecentlyUndoneTitle() ); + assertTrue( "custom Undo not called", action4.undoCalled() ); + assertFalse( "too many custom Undos called", action1.undoCalled() || action2.undoCalled() || action3.undoCalled() ); + i_undoManager.undo(); + assertTrue( "nested actions not properly undone", action1.undoCalled() && action2.undoCalled() && action3.undoCalled() ); + } + + + private void impl_testErrorHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.reset(); + m_undoListener.reset(); + + // try retrieving the comments for the current Undo/Redo - this should fail + boolean caughtExpected = false; + try { i_undoManager.getCurrentUndoActionTitle(); } + catch( final EmptyUndoStackException e ) { caughtExpected = true; } + assertTrue( "trying the title of the current Undo action is expected to fail for an empty stack", caughtExpected ); + + caughtExpected = false; + try { i_undoManager.getCurrentRedoActionTitle(); } + catch( final EmptyUndoStackException e ) { caughtExpected = true; } + assertTrue( "trying the title of the current Redo action is expected to fail for an empty stack", caughtExpected ); + + caughtExpected = false; + try { i_undoManager.undo(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; } + assertTrue( "undo should throw if no Undo action is on the stack", caughtExpected ); + + caughtExpected = false; + try { i_undoManager.redo(); } catch ( final EmptyUndoStackException e ) { caughtExpected = true; } + assertTrue( "redo should throw if no Redo action is on the stack", caughtExpected ); + + caughtExpected = false; + try { i_undoManager.leaveUndoContext(); } catch ( final InvalidStateException e ) { caughtExpected = true; } + assertTrue( "leaveUndoContext should throw if no context is currently open", caughtExpected ); + + caughtExpected = false; + try { i_undoManager.addUndoAction( null ); } catch ( com.sun.star.lang.IllegalArgumentException e ) { caughtExpected = true; } + assertTrue( "adding a NULL action should be rejected", caughtExpected ); + + i_undoManager.reset(); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.undo(); + i_undoManager.enterUndoContext( "Undo Context" ); + // those methods should fail when a context is open: + final String[] methodNames = new String[] { "undo", "redo", "clear", "clearRedo" }; + for ( int i=0; i<methodNames.length; ++i ) + { + caughtExpected = false; + try + { + Method method = i_undoManager.getClass().getMethod( methodNames[i], new Class[0] ); + method.invoke( i_undoManager, new Object[0] ); + } + catch ( IllegalAccessException ex ) { } + catch ( IllegalArgumentException ex ) { } + catch ( InvocationTargetException ex ) + { + Throwable targetException = ex.getTargetException(); + caughtExpected = ( targetException instanceof UndoContextNotClosedException ); + } + catch ( NoSuchMethodException ex ) { } + catch ( SecurityException ex ) { } + + assertTrue( methodNames[i] + " should be rejected when there is an open context", caughtExpected ); + } + i_undoManager.leaveUndoContext(); + + // try Undo actions which fail in their Undo/Redo + for ( int i=0; i<4; ++i ) + { + final boolean undo = ( i < 2 ); + final boolean doByAPI = ( i % 2 ) == 0; + + i_undoManager.reset(); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.addUndoAction( new FailingUndoAction( undo ? FAIL_UNDO : FAIL_REDO ) ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.undo(); + if ( !undo ) + i_undoManager.undo(); + // assert preconditions for the below test + assertTrue( i_undoManager.isUndoPossible() ); + assertTrue( i_undoManager.isRedoPossible() ); + + boolean caughtUndoFailed = false; + try + { + if ( undo ) + if ( doByAPI ) + i_undoManager.undo(); + else + m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Undo" ); + else + if ( doByAPI ) + i_undoManager.redo(); + else + m_currentTestCase.getDocument().getCurrentView().dispatch( ".uno:Redo" ); + } + catch ( UndoFailedException e ) + { + caughtUndoFailed = true; + } + if ( doByAPI ) + assertTrue( "Exceptions in XUndoAction.undo should be propagated at the API", caughtUndoFailed ); + else + assertFalse( "Undo/Redo by UI should not let escape Exceptions", caughtUndoFailed ); + if ( undo ) + { + assertFalse( "a failing Undo should clear the Undo stack", i_undoManager.isUndoPossible() ); + assertTrue( "a failing Undo should /not/ clear the Redo stack", i_undoManager.isRedoPossible() ); + } + else + { + assertTrue( "a failing Redo should /not/ clear the Undo stack", i_undoManager.isUndoPossible() ); + assertFalse( "a failing Redo should clear the Redo stack", i_undoManager.isRedoPossible() ); + } + } + } + + + private void impl_testStackHandling( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.reset(); + m_undoListener.reset(); + + assertFalse( i_undoManager.isUndoPossible() ); + assertFalse( i_undoManager.isRedoPossible() ); + + i_undoManager.addUndoAction( new CustomUndoAction() ); + assertTrue( i_undoManager.isUndoPossible() ); + assertFalse( i_undoManager.isRedoPossible() ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + assertTrue( i_undoManager.isUndoPossible() ); + assertFalse( i_undoManager.isRedoPossible() ); + i_undoManager.undo(); + assertTrue( i_undoManager.isUndoPossible() ); + assertTrue( i_undoManager.isRedoPossible() ); + i_undoManager.undo(); + assertFalse( i_undoManager.isUndoPossible() ); + assertTrue( i_undoManager.isRedoPossible() ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + assertTrue( i_undoManager.isUndoPossible() ); + assertFalse( "adding a new action should have cleared the Redo stack", i_undoManager.isRedoPossible() ); + } + + + private void impl_testClearance( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.reset(); + m_undoListener.reset(); + + // add an action, clear the stack, verify the listener has been called + i_undoManager.addUndoAction( new CustomUndoAction() ); + assertFalse( "clearance listener unexpectedly called", m_undoListener.wereStacksCleared() ); + assertFalse( "redo-clearance listener unexpectedly called", m_undoListener.wasRedoStackCleared() ); + i_undoManager.clear(); + assertTrue( "clearance listener not called as expected", m_undoListener.wereStacksCleared() ); + assertFalse( "redo-clearance listener unexpectedly called (2)", m_undoListener.wasRedoStackCleared() ); + + // ensure the listener is also called if the stack is actually empty at the moment of the call + m_undoListener.reset(); + assertFalse( i_undoManager.isUndoPossible() ); + i_undoManager.clear(); + assertTrue( "clearance listener is also expected to be called if the stack was empty before", m_undoListener.wereStacksCleared() ); + + // ensure the proper listeners are called for clearRedo + m_undoListener.reset(); + i_undoManager.clearRedo(); + assertFalse( m_undoListener.wereStacksCleared() ); + assertTrue( m_undoListener.wasRedoStackCleared() ); + + // ensure the redo listener is also called upon implicit redo stack clearance + m_undoListener.reset(); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.undo(); + assertTrue( i_undoManager.isUndoPossible() ); + assertTrue( i_undoManager.isRedoPossible() ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + assertFalse( i_undoManager.isRedoPossible() ); + assertTrue( "implicit clearance of the Redo stack does not notify listeners", m_undoListener.wasRedoStackCleared() ); + + // test resetting the manager + m_undoListener.reset(); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.undo(); + assertTrue( i_undoManager.isUndoPossible() ); + assertTrue( i_undoManager.isRedoPossible() ); + i_undoManager.reset(); + assertFalse( i_undoManager.isUndoPossible() ); + assertFalse( i_undoManager.isRedoPossible() ); + assertTrue( "|reset| does not properly notify", m_undoListener.wasManagerReset() ); + + // resetting the manager, with open undo contexts + m_undoListener.reset(); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.enterUndoContext( "Undo Context" ); + i_undoManager.addUndoAction( new CustomUndoAction() ); + i_undoManager.enterHiddenUndoContext(); + i_undoManager.reset(); + assertTrue( "|reset| while contexts are open does not properly notify", m_undoListener.wasManagerReset() ); + // verify the manager really has the proper context depth now + i_undoManager.enterUndoContext( "Undo Context" ); + assertEquals( "seems that |reset| did not really close the open contexts", 1, m_undoListener.getCurrentUndoContextDepth() ); + } + + + private void impl_testHiddenContexts( final XUndoManager i_undoManager ) throws com.sun.star.uno.Exception + { + i_undoManager.reset(); + m_undoListener.reset(); + assertFalse( "precondition for testing hidden undo contexts not met", i_undoManager.isUndoPossible() ); + + // entering a hidden context should be rejected if the stack is empty + boolean caughtExpected = false; + try { i_undoManager.enterHiddenUndoContext(); } + catch ( final EmptyUndoStackException e ) { caughtExpected = true; } + assertTrue( "entering hidden contexts should be denied on an empty stack", caughtExpected ); + + // but it should be allowed if the context is not empty + final CustomUndoAction undoAction0 = new CustomUndoAction( "Step 0" ); + i_undoManager.addUndoAction( undoAction0 ); + final CustomUndoAction undoAction1 = new CustomUndoAction( "Step 1" ); + i_undoManager.addUndoAction( undoAction1 ); + i_undoManager.enterHiddenUndoContext(); + final CustomUndoAction hiddenUndoAction = new CustomUndoAction( "hidden context action" ); + i_undoManager.addUndoAction( hiddenUndoAction ); + i_undoManager.leaveUndoContext(); + assertFalse( "leaving a hidden should not call |leftUndocontext|", m_undoListener.wasContextLeft() ); + assertTrue( "leaving a hidden does not call |leftHiddenUndocontext|", m_undoListener.wasHiddenContextLeft() ); + assertFalse( "leaving a non-empty hidden context claims to have cancelled it", m_undoListener.hasContextBeenCancelled() ); + assertEquals( "leaving a hidden context is not properly notified", 0, m_undoListener.getCurrentUndoContextDepth() ); + assertArrayEquals( "unexpected Undo stack after leaving a hidden context", + new String[] { undoAction1.getTitle(), undoAction0.getTitle() }, + i_undoManager.getAllUndoActionTitles() ); + + // and then calling |undo| once should not only undo everything in the hidden context, but also + // the previous action - but not more + i_undoManager.undo(); + assertTrue( "Undo after leaving a hidden context does not actually undo the context actions", + hiddenUndoAction.undoCalled() ); + assertTrue( "Undo after leaving a hidden context does not undo the predecessor action", + undoAction1.undoCalled() ); + assertFalse( "Undo after leaving a hidden context undoes too much", + undoAction0.undoCalled() ); + + // leaving an empty hidden context should call the proper notification method + m_undoListener.reset(); + i_undoManager.enterHiddenUndoContext(); + i_undoManager.leaveUndoContext(); + assertFalse( m_undoListener.wasContextLeft() ); + assertFalse( m_undoListener.wasHiddenContextLeft() ); + assertTrue( m_undoListener.hasContextBeenCancelled() ); + + // nesting hidden and normal contexts + m_undoListener.reset(); + i_undoManager.reset(); + final CustomUndoAction action0 = new CustomUndoAction( "action 0" ); + i_undoManager.addUndoAction( action0 ); + i_undoManager.enterUndoContext( "context 1" ); + final CustomUndoAction action1 = new CustomUndoAction( "action 1" ); + i_undoManager.addUndoAction( action1 ); + i_undoManager.enterHiddenUndoContext(); + final CustomUndoAction action2 = new CustomUndoAction( "action 2" ); + i_undoManager.addUndoAction( action2 ); + i_undoManager.enterUndoContext( "context 2" ); + // is entering a hidden context rejected even at the nesting level > 0 (the above test was for nesting level == 0)? + caughtExpected = false; + try { i_undoManager.enterHiddenUndoContext(); } + catch( final EmptyUndoStackException e ) { caughtExpected = true; } + assertTrue( "at a nesting level > 0, denied hidden contexts does not work as expected", caughtExpected ); + final CustomUndoAction action3 = new CustomUndoAction( "action 3" ); + i_undoManager.addUndoAction( action3 ); + i_undoManager.enterHiddenUndoContext(); + assertEquals( "mixed hidden/normal context do are not properly notified", 4, m_undoListener.getCurrentUndoContextDepth() ); + i_undoManager.leaveUndoContext(); + assertTrue( "the left context was empty - why wasn't 'cancelled' notified?", m_undoListener.hasContextBeenCancelled() ); + assertFalse( m_undoListener.wasContextLeft() ); + assertFalse( m_undoListener.wasHiddenContextLeft() ); + i_undoManager.leaveUndoContext(); + i_undoManager.leaveUndoContext(); + i_undoManager.leaveUndoContext(); + i_undoManager.undo(); + assertFalse( "one action too much has been undone", action0.undoCalled() ); + assertTrue( action1.undoCalled() ); + assertTrue( action2.undoCalled() ); + assertTrue( action3.undoCalled() ); + } + + + private XComponentContext getContext() + { + return m_connection.getComponentContext(); + } + + + private XMultiServiceFactory getORB() + { + final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface( + XMultiServiceFactory.class, getContext().getServiceManager() ); + return xMSF1; + } + + + @BeforeClass + public static void setUpConnection() throws java.lang.Exception + { + System.out.println( "--------------------------------------------------------------------------------" ); + System.out.println( "starting class: " + UndoManager.class.getName() ); + System.out.println( "connecting ..." ); + m_connection.setUp(); + } + + + @AfterClass + public static void tearDownConnection() throws InterruptedException, com.sun.star.uno.Exception + { + System.out.println(); + System.out.println( "tearing down connection" ); + m_connection.tearDown(); + System.out.println( "finished class: " + UndoManager.class.getName() ); + System.out.println( "--------------------------------------------------------------------------------" ); + } + + + private static class CustomUndoAction implements XUndoAction, XComponent + { + CustomUndoAction() + { + m_title = "Custom Undo Action"; + } + + CustomUndoAction( final String i_title ) + { + m_title = i_title; + } + + public String getTitle() + { + return m_title; + } + + public void undo() throws UndoFailedException + { + m_undoCalled = true; + } + + public void redo() throws UndoFailedException + { + } + + public void dispose() + { + m_disposed = true; + } + + public void addEventListener( XEventListener xl ) + { + fail( "addEventListener is not expected to be called in the course of this test" ); + } + + public void removeEventListener( XEventListener xl ) + { + fail( "removeEventListener is not expected to be called in the course of this test" ); + } + + boolean undoCalled() { return m_undoCalled; } + boolean disposed() { return m_disposed; } + + private final String m_title; + private boolean m_undoCalled = false; + private boolean m_disposed = false; + } + + private static short FAIL_UNDO = 1; + private static short FAIL_REDO = 2; + + private static class FailingUndoAction implements XUndoAction + { + FailingUndoAction( final short i_failWhich ) + { + m_failWhich = i_failWhich; + } + + public String getTitle() + { + return "failing undo"; + } + + public void undo() throws UndoFailedException + { + if ( m_failWhich != FAIL_REDO ) + impl_throw(); + } + + public void redo() throws UndoFailedException + { + if ( m_failWhich != FAIL_UNDO ) + impl_throw(); + } + + private void impl_throw() throws UndoFailedException + { + throw new UndoFailedException(); + } + + private final short m_failWhich; + } + + + private static class CountingUndoAction implements XUndoAction + { + CountingUndoAction( final int i_expectedOrder, final Object i_lock, final Integer[] i_actionsUndoneCounter ) + { + m_expectedOrder = i_expectedOrder; + m_lock = i_lock; + m_actionsUndoneCounter = i_actionsUndoneCounter; + } + + public String getTitle() + { + return "Counting Undo Action"; + } + + public void undo() throws UndoFailedException + { + synchronized( m_lock ) + { + assertEquals( "Undo action called out of order", m_expectedOrder, m_actionsUndoneCounter[0].intValue() ); + ++m_actionsUndoneCounter[0]; + } + } + + public void redo() throws UndoFailedException + { + fail( "CountingUndoAction.redo is not expected to be called in this test." ); + } + private final int m_expectedOrder; + private final Object m_lock; + private Integer[] m_actionsUndoneCounter; + } + + + private static String getCallbackUndoContextTitle() + { + return "Some Unfinished Undo Context"; + } + + + private static String getCallbackComponentServiceName() + { + return "org.openoffice.complex.sfx2.Callback"; + } + + + /** + * a factory for a callback component which, at OOo runtime, is inserted into OOo's "component repository" + */ + private class CallbackComponentFactory implements XSingleComponentFactory, XServiceInfo, XComponent + { + public Object createInstanceWithContext( XComponentContext i_context ) throws com.sun.star.uno.Exception + { + return new CallbackComponent(); + } + + public Object createInstanceWithArgumentsAndContext( Object[] i_arguments, XComponentContext i_context ) throws com.sun.star.uno.Exception + { + return createInstanceWithContext( i_context ); + } + + public String getImplementationName() + { + return "org.openoffice.complex.sfx2.CallbackComponent"; + } + + public boolean supportsService( String i_serviceName ) + { + return i_serviceName.equals( getCallbackComponentServiceName() ); + } + + public String[] getSupportedServiceNames() + { + return new String[] { getCallbackComponentServiceName() }; + } + + public void dispose() + { + final EventObject event = new EventObject( this ); + + final ArrayList<XEventListener> eventListenersCopy = (ArrayList<XEventListener>)m_eventListeners.clone(); + final Iterator<XEventListener> iter = eventListenersCopy.iterator(); + while ( iter.hasNext() ) + { + iter.next().disposing( event ); + } + } + + public void addEventListener( XEventListener i_listener ) + { + if ( i_listener != null ) + m_eventListeners.add( i_listener ); + } + + public void removeEventListener( XEventListener i_listener ) + { + m_eventListeners.remove( i_listener ); + } + + private final ArrayList<XEventListener> m_eventListeners = new ArrayList<XEventListener>(); + } + + + private class CallbackComponent implements XJob, XTypeProvider + { + CallbackComponent() + { + } + + public Object execute( NamedValue[] i_parameters ) throws com.sun.star.lang.IllegalArgumentException, com.sun.star.uno.Exception + { + // this method is called from within the Basic script which is to check whether the OOo framework + // properly cleans up unfinished Undo contexts. It is called immediately after the context has been + // entered, so verify the expected Undo manager state. + assertEquals( getCallbackUndoContextTitle(), m_undoListener.getCurrentUndoContextTitle() ); + assertEquals( 1, m_undoListener.getCurrentUndoContextDepth() ); + + synchronized( m_callbackCondition ) + { + m_callbackCalled = true; + m_callbackCondition.notifyAll(); + } + return m_closeAfterCallback ? "close" : ""; + } + + public Type[] getTypes() + { + final Class<?> interfaces[] = getClass().getInterfaces(); + Type types[] = new Type[ interfaces.length ]; + for ( int i = 0; i < interfaces.length; ++i ) + types[i] = new Type(interfaces[i]); + return types; + } + + public byte[] getImplementationId() + { + return new byte[0]; + } + } + + private static final OfficeConnection m_connection = new OfficeConnection(); + private DocumentTest m_currentTestCase; + private OfficeDocument m_currentDocument; + private UndoListener m_undoListener; + private CallbackComponentFactory m_callbackFactory = null; + private boolean m_callbackCalled = false; + private boolean m_closeAfterCallback = false; + private final Object m_callbackCondition = new Object(); +} diff --git a/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt b/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt Binary files differnew file mode 100644 index 000000000..831a8f451 --- /dev/null +++ b/sfx2/qa/complex/sfx2/testdocuments/CUSTOM.odt diff --git a/sfx2/qa/complex/sfx2/testdocuments/TEST.odt b/sfx2/qa/complex/sfx2/testdocuments/TEST.odt Binary files differnew file mode 100644 index 000000000..7c6f0b60f --- /dev/null +++ b/sfx2/qa/complex/sfx2/testdocuments/TEST.odt diff --git a/sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odt b/sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odt Binary files differnew file mode 100644 index 000000000..d59739142 --- /dev/null +++ b/sfx2/qa/complex/sfx2/testdocuments/TESTRDFA.odt diff --git a/sfx2/qa/complex/sfx2/testdocuments/empty.rdf b/sfx2/qa/complex/sfx2/testdocuments/empty.rdf new file mode 100644 index 000000000..af62bab39 --- /dev/null +++ b/sfx2/qa/complex/sfx2/testdocuments/empty.rdf @@ -0,0 +1,13 @@ +<?xml version="1.0"?> + +<RDF + xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:s="http://www.w3.org/2000/01/rdf-schema#"> + +<!-- + This is the RDF Schema for the RDF data model as described in the + Resource Description Framework (RDF) Model and Syntax Specification + http://www.w3.org/TR/REC-rdf-syntax --> + +</RDF> diff --git a/sfx2/qa/complex/sfx2/tools/TestDocument.java b/sfx2/qa/complex/sfx2/tools/TestDocument.java new file mode 100644 index 000000000..462c97fb9 --- /dev/null +++ b/sfx2/qa/complex/sfx2/tools/TestDocument.java @@ -0,0 +1,34 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.tools; + +import java.io.File; +import org.openoffice.test.OfficeFileUrl; +import org.openoffice.test.Argument; + +public final class TestDocument { + public static String getUrl(String name) { + return OfficeFileUrl.getAbsolute(new File(Argument.get("tdoc"), name)); + } + public static String getPath(String name) { + return new File(Argument.get("tdoc"), name).toString(); + } + + private TestDocument() {} +} diff --git a/sfx2/qa/complex/sfx2/tools/WriterHelper.java b/sfx2/qa/complex/sfx2/tools/WriterHelper.java new file mode 100644 index 000000000..3180d3607 --- /dev/null +++ b/sfx2/qa/complex/sfx2/tools/WriterHelper.java @@ -0,0 +1,210 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +package complex.sfx2.tools; + +import com.sun.star.accessibility.AccessibleRole; +import com.sun.star.accessibility.XAccessible; +import com.sun.star.accessibility.XAccessibleAction; +import com.sun.star.accessibility.XAccessibleContext; +import com.sun.star.awt.XExtendedToolkit; +import com.sun.star.awt.XWindow; +import com.sun.star.beans.PropertyValue; +import com.sun.star.frame.XController; +import com.sun.star.frame.XDesktop; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.frame.XModel; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.text.XTextDocument; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.URL; +import com.sun.star.util.XCloseable; +import com.sun.star.util.XURLTransformer; + +import util.AccessibilityTools; +import util.WriterTools; + +/** + * Methods to open Writer docs + * + */ +public class WriterHelper { + private XMultiServiceFactory m_xMSF = null; + + /** + * Creates a new instance of WriterHelper + * + * @param xMSF + * The MultiServiceFactory gained from the office + */ + public WriterHelper(XMultiServiceFactory xMSF) { + this.m_xMSF = xMSF; + } + + /** + * Opens an empty document + * + * @return a reference to the opened document is returned + */ + public XTextDocument openEmptyDoc() { + return WriterTools.createTextDoc(m_xMSF); + } + + /** + * Closes a given XTextDocument + * + * @param xTextDoc + * the text document to be closed + * @return if an error occurs the errormessage is returned and an empty + * String if not + */ + public String closeDoc(XTextDocument xTextDoc) { + XCloseable closer = UnoRuntime.queryInterface(XCloseable.class, + xTextDoc); + String err = ""; + + try { + closer.close(true); + } catch (com.sun.star.util.CloseVetoException e) { + err = "couldn't close document " + e; + System.out.println(err); + } + + return err; + } + + private XTextDocument xLocalDoc = null; + + /** + * a TextDocument is opened by pressing a button in a dialog given by + * uno-URL + * + * @param url + * the uno-URL of the dialog to be opened + * @param createButton + * the language dependent label of the button to be pressed + * @param destroyLocal + * if true the document that has been opened to dispatch the + * dialog is closed before the method returns, otherwise this + * document remains open + * @return returns the created Textdocument + */ + public XTextDocument openFromDialog(String url, String createButton, + boolean destroyLocal) throws Exception { + xLocalDoc = WriterTools.createTextDoc(m_xMSF); + XComponent comp = UnoRuntime + .queryInterface(XComponent.class, xLocalDoc); + + XModel aModel = UnoRuntime.queryInterface(XModel.class, comp); + + XController xController = aModel.getCurrentController(); + + // Opening Dialog + XDispatchProvider xDispProv = UnoRuntime.queryInterface( + XDispatchProvider.class, xController.getFrame()); + XURLTransformer xParser = UnoRuntime.queryInterface( + XURLTransformer.class, + m_xMSF.createInstance("com.sun.star.util.URLTransformer")); + + // Because it's an in/out parameter + // we must use an array of URL objects. + URL[] aParseURL = new URL[] { new URL() }; + aParseURL[0].Complete = url; + xParser.parseStrict(aParseURL); + + XDispatch xDispatcher = xDispProv.queryDispatch(aParseURL[0], "", + com.sun.star.frame.FrameSearchFlag.SELF + | com.sun.star.frame.FrameSearchFlag.CHILDREN); + if (xDispatcher != null) { + PropertyValue[] dispatchArguments = new PropertyValue[0]; + xDispatcher.dispatch(aParseURL[0], dispatchArguments); + } + + if (createButton.length() > 1) { + XExtendedToolkit tk = getToolkit(); + Object atw = tk.getActiveTopWindow(); + + XWindow xWindow = UnoRuntime.queryInterface(XWindow.class, atw); + + XAccessible xRoot = AccessibilityTools.getAccessibleObject(xWindow); + XAccessibleContext buttonContext = AccessibilityTools + .getAccessibleObjectForRole(xRoot, + AccessibleRole.PUSH_BUTTON, createButton); + + XAccessibleAction buttonAction = UnoRuntime.queryInterface( + XAccessibleAction.class, buttonContext); + + try { + System.out + .println("Name: " + buttonContext.getAccessibleName()); + buttonAction.doAccessibleAction(0); + } catch (com.sun.star.lang.IndexOutOfBoundsException e) { + System.out.println("Couldn't press button"); + } + + util.utils.waitForEventIdle(m_xMSF); + } + + XDesktop xDesktop = getDesktop(); + + XTextDocument returnDoc = UnoRuntime.queryInterface( + XTextDocument.class, xDesktop.getCurrentComponent()); + + if (destroyLocal) { + closeDoc(xLocalDoc); + xLocalDoc = null; + } + + return returnDoc; + } + + public void closeFromDialog() { + closeDoc(xLocalDoc); + xLocalDoc = null; + } + + /** + * creates an instance of com.sun.star.awt.Toolkit to query the + * XExtendedToolkit interface + * + * @return returns the gained XExtendedToolkit Interface + */ + public XExtendedToolkit getToolkit() throws com.sun.star.uno.Exception { + Object toolkit = m_xMSF.createInstance("com.sun.star.awt.Toolkit"); + + XExtendedToolkit tk = UnoRuntime.queryInterface(XExtendedToolkit.class, + toolkit); + + return tk; + } + + /** + * creates an instance of com.sun.star.frame.Desktop to query the XDesktop + * interface + * + * @return returns the gained XDesktop interface + */ + private XDesktop getDesktop() throws com.sun.star.uno.Exception { + Object desk = m_xMSF.createInstance("com.sun.star.frame.Desktop"); + + XDesktop xDesktop = UnoRuntime.queryInterface(XDesktop.class, desk); + + return xDesktop; + } +}
\ No newline at end of file diff --git a/sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java b/sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java new file mode 100644 index 000000000..a0dec8d06 --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/CalcDocumentTest.java @@ -0,0 +1,113 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import org.openoffice.test.tools.SpreadsheetDocument; +import com.sun.star.table.XCellRange; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.table.XCell; +import com.sun.star.uno.UnoRuntime; +import org.openoffice.test.tools.DocumentType; +import static org.junit.Assert.*; + +/** + * implements the {@link DocumentTest} interface on top of a spreadsheet document + */ +public class CalcDocumentTest extends DocumentTestBase +{ + public CalcDocumentTest( final XMultiServiceFactory i_orb ) throws Exception + { + super( i_orb, DocumentType.CALC ); + } + + public String getDocumentDescription() + { + return "spreadsheet document"; + } + + public void initializeDocument() throws com.sun.star.uno.Exception + { + final XCell cellA1 = getCellA1(); + cellA1.setValue( INIT_VALUE ); + assertEquals( "initializing the cell value didn't work", cellA1.getValue(), INIT_VALUE, 0 ); + + XCellRange range = UnoRuntime.queryInterface( XCellRange.class, + ((SpreadsheetDocument)m_document).getSheet(0) ); + + for ( int i=0; i<12; ++i ) + { + XCell cell = range.getCellByPosition( 1, i ); + cell.setFormula( "" ); + } + } + + public void doSingleModification() throws com.sun.star.uno.Exception + { + final XCell cellA1 = getCellA1(); + assertEquals( "initial cell value not as expected", INIT_VALUE, cellA1.getValue(), 0 ); + cellA1.setValue( MODIFIED_VALUE ); + assertEquals( "modified cell value not as expected", MODIFIED_VALUE, cellA1.getValue(), 0 ); + } + + public void verifyInitialDocumentState() throws com.sun.star.uno.Exception + { + final XCell cellA1 = getCellA1(); + assertEquals( "cell A1 doesn't have its initial value", INIT_VALUE, cellA1.getValue(), 0 ); + + XCellRange range = UnoRuntime.queryInterface( XCellRange.class, + ((SpreadsheetDocument)m_document).getSheet(0) ); + for ( int i=0; i<12; ++i ) + { + final XCell cell = range.getCellByPosition( 1, i ); + assertEquals( "Cell B" + (i+1) + " not having its initial value (an empty string)", "", cell.getFormula() ); + } + } + + public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception + { + final XCell cellA1 = getCellA1(); + assertEquals( "cell A1 doesn't have the value which we gave it", MODIFIED_VALUE, cellA1.getValue(), 0 ); + } + + public int doMultipleModifications() throws com.sun.star.uno.Exception + { + XCellRange range = UnoRuntime.queryInterface( XCellRange.class, + ((SpreadsheetDocument)m_document).getSheet(0) ); + + final String[] months = new String[] { + "January", "February", "March", "April", "May", "June", "July", "August", + "September", "October", "November", "December" }; + for ( int i=0; i<12; ++i ) + { + final XCell cell = range.getCellByPosition( 1, i ); + cell.setFormula( months[i] ); + } + return 12; + } + + private XCell getCellA1() throws com.sun.star.uno.Exception + { + XCellRange range = UnoRuntime.queryInterface( XCellRange.class, + ((SpreadsheetDocument)m_document).getSheet(0) ); + return range.getCellByPosition( 0, 0 ); + } + + private static final double INIT_VALUE = 100.0; + private static final double MODIFIED_VALUE = 200.0; +} diff --git a/sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java b/sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java new file mode 100644 index 000000000..5d5ea95b2 --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/ChartDocumentTest.java @@ -0,0 +1,257 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import com.sun.star.chart2.XAxis; +import com.sun.star.chart2.XCoordinateSystem; +import com.sun.star.chart2.XCoordinateSystemContainer; +import com.sun.star.awt.Size; +import com.sun.star.beans.XPropertySet; +import com.sun.star.chart2.XChartDocument; +import com.sun.star.chart2.XDiagram; +import com.sun.star.container.XIndexAccess; +import com.sun.star.document.UndoFailedException; +import com.sun.star.document.XUndoAction; +import com.sun.star.document.XUndoManager; +import com.sun.star.document.XUndoManagerSupplier; +import com.sun.star.drawing.XShape; +import com.sun.star.embed.EmbedStates; +import com.sun.star.embed.EmbedVerbs; +import com.sun.star.embed.XEmbeddedObject; +import com.sun.star.lang.IndexOutOfBoundsException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextRange; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.view.XSelectionSupplier; +import org.openoffice.test.tools.DocumentType; +import org.openoffice.test.tools.OfficeDocument; +import static org.junit.Assert.*; + +public class ChartDocumentTest implements DocumentTest +{ + public ChartDocumentTest( final XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception + { + m_textDocument = OfficeDocument.blankDocument( i_orb, DocumentType.WRITER ); + + // create an OLE shape in the document + final XMultiServiceFactory factory = UnoRuntime.queryInterface( XMultiServiceFactory.class, m_textDocument.getDocument() ); + final String shapeServiceName = "com.sun.star.text.TextEmbeddedObject"; + final XPropertySet shapeProps = UnoRuntime.queryInterface( XPropertySet.class, factory.createInstance( shapeServiceName ) ); + shapeProps.setPropertyValue("CLSID", "12dcae26-281f-416f-a234-c3086127382e"); + + final XShape shape = UnoRuntime.queryInterface( XShape.class, shapeProps ); + shape.setSize( new Size( 16000, 9000 ) ); + + final XTextContent chartTextContent = UnoRuntime.queryInterface( XTextContent.class, shapeProps ); + + final XSelectionSupplier selSupplier = UnoRuntime.queryInterface( XSelectionSupplier.class, + m_textDocument.getCurrentView().getController() ); + final Object selection = selSupplier.getSelection(); + final XTextRange textRange = getAssociatedTextRange( selection ); + if ( textRange == null ) + throw new RuntimeException( "can't locate a text range" ); + + // insert the chart + textRange.getText().insertTextContent(textRange, chartTextContent, false); + + // retrieve the chart model + XChartDocument chartDoc = UnoRuntime.queryInterface( XChartDocument.class, shapeProps.getPropertyValue( "Model" ) ); + // insert default chart for the test to use. + com.sun.star.chart2.XChartDocument xCD2 = + UnoRuntime.queryInterface(com.sun.star.chart2.XChartDocument.class, chartDoc); + xCD2.createDefaultChart(); + m_chartDocument = new OfficeDocument( i_orb, chartDoc ); + + // actually activate the object + final XEmbeddedObject embeddedChart = UnoRuntime.queryInterface( XEmbeddedObject.class, + shapeProps.getPropertyValue( "EmbeddedObject" ) ); + embeddedChart.doVerb( EmbedVerbs.MS_OLEVERB_SHOW ); + + final int state = embeddedChart.getCurrentState(); + if ( state != EmbedStates.UI_ACTIVE ) + fail( "unable to activate the embedded chart" ); + } + + public String getDocumentDescription() + { + return "chart document"; + } + + public void initializeDocument() throws com.sun.star.uno.Exception + { + final XPropertySet wallProperties = impl_getWallProperties(); + wallProperties.setPropertyValue( "FillStyle", com.sun.star.drawing.FillStyle.SOLID ); + wallProperties.setPropertyValue( "FillColor", 0x00FFFFFF ); + } + + public void closeDocument() + { + m_textDocument.close(); + } + + private XPropertySet impl_getWallProperties() + { + final XChartDocument chartDoc = UnoRuntime.queryInterface( XChartDocument.class, m_chartDocument.getDocument() ); + final XDiagram diagram = chartDoc.getFirstDiagram(); + final XPropertySet wallProperties = diagram.getWall(); + return wallProperties; + } + + private XPropertySet impl_getYAxisProperties() throws IndexOutOfBoundsException + { + XPropertySet axisProperties = null; + final XChartDocument chartDoc = UnoRuntime.queryInterface( XChartDocument.class, m_chartDocument.getDocument() ); + final XDiagram diagram = chartDoc.getFirstDiagram(); + final XCoordinateSystemContainer coordContainer = UnoRuntime.queryInterface( XCoordinateSystemContainer.class, diagram ); + final XCoordinateSystem[] coordSystems = coordContainer.getCoordinateSystems(); + final XCoordinateSystem coordSystem = coordSystems[0]; + final XAxis primaryYAxis = coordSystem.getAxisByDimension( 1, 0 ); + axisProperties = UnoRuntime.queryInterface( XPropertySet.class, primaryYAxis ); + return axisProperties; + } + + private XUndoManager impl_getUndoManager() + { + final XUndoManagerSupplier undoManagerSupp = UnoRuntime.queryInterface( XUndoManagerSupplier.class, m_chartDocument.getDocument() ); + final XUndoManager undoManager = undoManagerSupp.getUndoManager(); + return undoManager; + } + + public void doSingleModification() throws com.sun.star.uno.Exception + { + final XPropertySet wallProperties = impl_getWallProperties(); + + // simulate an Undo action, as long as the chart implementation doesn't add Undo actions itself + final XUndoManager undoManager = impl_getUndoManager(); + undoManager.addUndoAction( new PropertyUndoAction( wallProperties, "FillColor", 0xCCFF44 ) ); + // (the UndoAction will actually set the property value) + } + + public void verifyInitialDocumentState() throws com.sun.star.uno.Exception + { + final XPropertySet wallProperties = impl_getWallProperties(); + assertEquals( 0x00FFFFFF, ((Integer)wallProperties.getPropertyValue( "FillColor" )).intValue() ); + } + + public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception + { + final XPropertySet wallProperties = impl_getWallProperties(); + assertEquals( 0xCCFF44, ((Integer)wallProperties.getPropertyValue( "FillColor" )).intValue() ); + } + + public int doMultipleModifications() throws com.sun.star.uno.Exception + { + final XPropertySet axisProperties = impl_getYAxisProperties(); + + final XUndoManager undoManager = impl_getUndoManager(); + undoManager.addUndoAction( new PropertyUndoAction( axisProperties, "LineWidth", 300 ) ); + undoManager.addUndoAction( new PropertyUndoAction( axisProperties, "LineColor", 0x000000 ) ); + + return 2; + } + + public OfficeDocument getDocument() + { + return m_chartDocument; + } + + private XTextRange getAssociatedTextRange( final Object i_object ) throws WrappedTargetException, IndexOutOfBoundsException + { + // possible cases: + // 1. a container of other objects - e.g. selection of 0 to n text portions, or 1 to n drawing objects + final XIndexAccess indexer = UnoRuntime.queryInterface( XIndexAccess.class, i_object ); + if ((indexer != null) && indexer.getCount() > 0) { + final int count = indexer.getCount(); + for (int i = 0; i < count; ++i) { + final XTextRange range = getAssociatedTextRange( indexer.getByIndex(i) ); + if (range != null) { + return range; + } + } + } + // 2. another TextContent, having an anchor we can use + final XTextContent textContent = UnoRuntime.queryInterface(XTextContent.class, i_object); + if (textContent != null) { + final XTextRange range = textContent.getAnchor(); + if (range != null) { + return range; + } + } + + // an object which supports XTextRange directly + final XTextRange range = UnoRuntime.queryInterface(XTextRange.class, i_object); + if (range != null) { + return range; + } + + return null; + } + + private static class PropertyUndoAction implements XUndoAction + { + PropertyUndoAction( final XPropertySet i_component, final String i_propertyName, final Object i_newValue ) throws com.sun.star.uno.Exception + { + m_component = i_component; + m_propertyName = i_propertyName; + m_newValue = i_newValue; + + m_oldValue = i_component.getPropertyValue( m_propertyName ); + i_component.setPropertyValue( m_propertyName, m_newValue ); + } + + public String getTitle() + { + return "some dummy Undo Action"; + } + + public void undo() throws UndoFailedException + { + try + { + m_component.setPropertyValue( m_propertyName, m_oldValue ); + } + catch ( com.sun.star.uno.Exception ex ) + { + throw new UndoFailedException( "", this, ex ); + } + } + + public void redo() throws UndoFailedException + { + try + { + m_component.setPropertyValue( m_propertyName, m_newValue ); + } + catch ( com.sun.star.uno.Exception ex ) + { + throw new UndoFailedException( "", this, ex ); + } + } + + private final XPropertySet m_component; + private final String m_propertyName; + private final Object m_oldValue; + private final Object m_newValue; + } + + private final OfficeDocument m_textDocument; + private final OfficeDocument m_chartDocument; +} diff --git a/sfx2/qa/complex/sfx2/undo/DocumentTest.java b/sfx2/qa/complex/sfx2/undo/DocumentTest.java new file mode 100644 index 000000000..dd5fe4cf7 --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/DocumentTest.java @@ -0,0 +1,77 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import org.openoffice.test.tools.OfficeDocument; + +/** + * wrapper around an OfficeDocument, for running a standardized test procedure (related do Undo functionality) + * on the document. + * + */ +public interface DocumentTest +{ + /** + * returns a human-readable description for the document/type which the tests operates on + */ + public String getDocumentDescription(); + + /** + * initializes the document to a state where the subsequent tests can be ran + */ + public void initializeDocument() throws com.sun.star.uno.Exception; + + /** + * closes the document which the test is based on + */ + public void closeDocument(); + + /** + * does a simple modification to the document, which results in one Undo action being auto-generated + * by the OOo implementation + */ + public void doSingleModification() throws com.sun.star.uno.Exception; + + /** + * verifies the document is in the same state as after {@link #initializeDocument} + */ + public void verifyInitialDocumentState() throws com.sun.star.uno.Exception; + + /** + * verifies the document is in the state as expected after {@link #doSingleModification} + */ + public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception; + + /** + * does multiple modifications do the document, which would normally result in multiple Undo actions. + * + * The test framework will encapsulate the call into an {@link com.sun.star.document.XUndoManager#enterUndoContext} and + * {@link com.sun.star.document.XUndoManager#leaveUndoContext} call. + * + * @return + * the number of modifications done to the document. The caller assumes (and asserts) that the number + * of actions on the Undo stack equals this number. + */ + public int doMultipleModifications() throws com.sun.star.uno.Exception; + + /** + * returns the document which the test operates on + */ + public OfficeDocument getDocument(); +} diff --git a/sfx2/qa/complex/sfx2/undo/DocumentTestBase.java b/sfx2/qa/complex/sfx2/undo/DocumentTestBase.java new file mode 100644 index 000000000..d7a8c111b --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/DocumentTestBase.java @@ -0,0 +1,44 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.Exception; +import org.openoffice.test.tools.DocumentType; +import org.openoffice.test.tools.OfficeDocument; + +abstract class DocumentTestBase implements DocumentTest +{ + DocumentTestBase( final XMultiServiceFactory i_orb, final DocumentType i_docType ) throws Exception + { + m_document = OfficeDocument.blankDocument( i_orb, i_docType ); + } + + public OfficeDocument getDocument() + { + return m_document; + } + + public void closeDocument() + { + m_document.close(); + } + + protected final OfficeDocument m_document; +} diff --git a/sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java b/sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java new file mode 100644 index 000000000..ef4ff7fa0 --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/DrawDocumentTest.java @@ -0,0 +1,35 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import com.sun.star.lang.XMultiServiceFactory; +import org.openoffice.test.tools.DocumentType; + +public class DrawDocumentTest extends DrawingOrPresentationDocumentTest +{ + public DrawDocumentTest( XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception + { + super( i_orb, DocumentType.DRAWING ); + } + + public String getDocumentDescription() + { + return "drawing document"; + } +} diff --git a/sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java b/sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java new file mode 100644 index 000000000..37b5d0d9c --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/DrawingOrPresentationDocumentTest.java @@ -0,0 +1,207 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import com.sun.star.awt.Rectangle; +import com.sun.star.document.XUndoManager; +import com.sun.star.document.XUndoManagerSupplier; +import com.sun.star.document.XUndoAction; +import com.sun.star.awt.Point; +import com.sun.star.awt.Size; +import com.sun.star.beans.XPropertySet; +import com.sun.star.drawing.CircleKind; +import com.sun.star.drawing.XDrawPages; +import com.sun.star.drawing.XDrawPagesSupplier; +import com.sun.star.drawing.XShape; +import com.sun.star.drawing.XShapes; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import org.openoffice.test.tools.DocumentType; +import static org.junit.Assert.*; + +/** + * implements the {@link DocumentTest} interface on top of a drawing document + */ +public abstract class DrawingOrPresentationDocumentTest extends DocumentTestBase +{ + public DrawingOrPresentationDocumentTest( XMultiServiceFactory i_orb, final DocumentType i_docType ) throws com.sun.star.uno.Exception + { + super( i_orb, i_docType ); + } + + public void initializeDocument() throws com.sun.star.uno.Exception + { + // remove all shapes - Impress has two default shapes in a new doc; just get rid of them + final XShapes firstPageShapes = getFirstPageShapes(); + while ( firstPageShapes.getCount() > 0 ) + firstPageShapes.remove( UnoRuntime.queryInterface( XShape.class, firstPageShapes.getByIndex( 0 ) ) ); + } + + public void doSingleModification() throws com.sun.star.uno.Exception + { + // add a simple centered shape to the first page + Rectangle pagePlayground = impl_getFirstPagePlayground(); + impl_createCircleShape( + ( pagePlayground.X + ( pagePlayground.Width - BIG_CIRCLE_SIZE ) / 2 ), + ( pagePlayground.Y + ( pagePlayground.Height - BIG_CIRCLE_SIZE ) / 2 ), + BIG_CIRCLE_SIZE, + FILL_COLOR + ); + } + + public void verifyInitialDocumentState() throws com.sun.star.uno.Exception + { + final XShapes firstPageShapes = getFirstPageShapes(); + assertEquals( "there should be no shapes at all", 0, firstPageShapes.getCount() ); + } + + public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception + { + final XShapes firstPageShapes = getFirstPageShapes(); + assertEquals( "there should be one shape, not more, not less", 1, firstPageShapes.getCount() ); + + final Object shape = firstPageShapes.getByIndex(0); + verifyShapeGeometry( shape, BIG_CIRCLE_SIZE, BIG_CIRCLE_SIZE ); + final XPropertySet shapeProps = UnoRuntime.queryInterface( XPropertySet.class, shape ); + assertEquals( "wrong circle type", CIRCLE_TYPE.getValue(), ((CircleKind)shapeProps.getPropertyValue( "CircleKind" )).getValue() ); + //assertEquals( "wrong circle fill color", FILL_COLOR, ((Integer)shapeProps.getPropertyValue( "FillColor" )).intValue() ); + // disable this particular check: A bug in the drawing layer API restores the FillColor to its + // default value upon re-insertion. This is issue #i115080# + } + + public int doMultipleModifications() throws com.sun.star.uno.Exception + { + // add a simple centered shape to the first page + Rectangle pagePlayground = impl_getFirstPagePlayground(); + impl_createCircleShape( + pagePlayground.X, + pagePlayground.Y, + SMALL_CIRCLE_SIZE, + ALTERNATE_FILL_COLOR + ); + impl_createCircleShape( + pagePlayground.X + pagePlayground.Width - SMALL_CIRCLE_SIZE, + pagePlayground.Y, + SMALL_CIRCLE_SIZE, + ALTERNATE_FILL_COLOR + ); + impl_createCircleShape( + pagePlayground.X, + pagePlayground.Y + pagePlayground.Height - SMALL_CIRCLE_SIZE, + SMALL_CIRCLE_SIZE, + ALTERNATE_FILL_COLOR + ); + impl_createCircleShape( + pagePlayground.X + pagePlayground.Width - SMALL_CIRCLE_SIZE, + pagePlayground.Y + pagePlayground.Height - SMALL_CIRCLE_SIZE, + SMALL_CIRCLE_SIZE, + ALTERNATE_FILL_COLOR + ); + return 4; + } + + private void impl_createCircleShape( final int i_x, final int i_y, final int i_size, final int i_color ) throws com.sun.star.uno.Exception + { + final XPropertySet shapeProps = getDocument().createInstance( "com.sun.star.drawing.EllipseShape", XPropertySet.class ); + shapeProps.setPropertyValue( "CircleKind", CIRCLE_TYPE ); + shapeProps.setPropertyValue( "FillColor", i_color ); + + final XShape shape = UnoRuntime.queryInterface( XShape.class, shapeProps ); + final Size shapeSize = new Size( i_size, i_size ); + shape.setSize( shapeSize ); + final Point shapePos = new Point( i_x, i_y ); + shape.setPosition( shapePos ); + + final XShapes pageShapes = UnoRuntime.queryInterface( XShapes.class, getFirstPageShapes() ); + pageShapes.add( shape ); + + // Sadly, Draw/Impress currently do not create Undo actions for programmatic changes to the document. + // Which renders the test here slightly useless ... unless we fake the Undo actions ourself. + final XUndoManagerSupplier suppUndoManager = UnoRuntime.queryInterface( XUndoManagerSupplier.class, getDocument().getDocument() ); + final XUndoManager undoManager = suppUndoManager.getUndoManager(); + undoManager.addUndoAction( new ShapeInsertionUndoAction( shape, pageShapes ) ); + } + + private Rectangle impl_getFirstPagePlayground() throws com.sun.star.uno.Exception + { + final XShapes firstPageShapes = getFirstPageShapes(); + final XPropertySet firstPageProps = UnoRuntime.queryInterface( XPropertySet.class, firstPageShapes ); + final int pageWidth = ((Integer)firstPageProps.getPropertyValue( "Width" )).intValue(); + final int pageHeight = ((Integer)firstPageProps.getPropertyValue( "Height" )).intValue(); + final int borderLeft = ((Integer)firstPageProps.getPropertyValue( "BorderLeft" )).intValue(); + final int borderTop = ((Integer)firstPageProps.getPropertyValue( "BorderTop" )).intValue(); + final int borderRight = ((Integer)firstPageProps.getPropertyValue( "BorderRight" )).intValue(); + final int borderBottom = ((Integer)firstPageProps.getPropertyValue( "BorderBottom" )).intValue(); + return new Rectangle( borderLeft, borderTop, pageWidth - borderLeft - borderRight, pageHeight - borderTop - borderBottom ); + } + + /** + * returns the XShapes interface of the first page of our drawing document + */ + private XShapes getFirstPageShapes() throws com.sun.star.uno.Exception + { + final XDrawPagesSupplier suppPages = UnoRuntime.queryInterface( XDrawPagesSupplier.class, getDocument().getDocument() ); + final XDrawPages pages = suppPages.getDrawPages(); + return UnoRuntime.queryInterface( XShapes.class, pages.getByIndex( 0 ) ); + } + + /** + * verifies the given shape has the given size + */ + private void verifyShapeGeometry( final Object i_shapeObject, final int i_expectedWidth, final int i_expectedHeight ) + { + final XShape shape = UnoRuntime.queryInterface( XShape.class, i_shapeObject ); + final Size shapeSize = shape.getSize(); + assertEquals( "unexpected shape width", i_expectedWidth, shapeSize.Width ); + assertEquals( "unexpected shape height", i_expectedHeight, shapeSize.Height ); + } + + private static class ShapeInsertionUndoAction implements XUndoAction + { + ShapeInsertionUndoAction( final XShape i_shape, final XShapes i_shapeCollection ) + { + m_shape = i_shape; + m_shapeCollection = i_shapeCollection; + } + + public String getTitle() + { + return "insert shape"; + } + + public void undo() + { + m_shapeCollection.remove( m_shape ); + } + + public void redo() + { + m_shapeCollection.add( m_shape ); + } + + private final XShape m_shape; + private final XShapes m_shapeCollection; + } + + private static CircleKind CIRCLE_TYPE = CircleKind.FULL; + private static int FILL_COLOR = 0xCC2244; + private static int ALTERNATE_FILL_COLOR = 0x44CC22; + private static int BIG_CIRCLE_SIZE = 5000; + private static int SMALL_CIRCLE_SIZE = 2000; +} diff --git a/sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java b/sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java new file mode 100644 index 000000000..8f9cb5771 --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/ImpressDocumentTest.java @@ -0,0 +1,35 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import com.sun.star.lang.XMultiServiceFactory; +import org.openoffice.test.tools.DocumentType; + +public class ImpressDocumentTest extends DrawingOrPresentationDocumentTest +{ + public ImpressDocumentTest( XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception + { + super( i_orb, DocumentType.PRESENTATION ); + } + + public String getDocumentDescription() + { + return "presentation document"; + } +} diff --git a/sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java b/sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java new file mode 100644 index 000000000..624c2d7d2 --- /dev/null +++ b/sfx2/qa/complex/sfx2/undo/WriterDocumentTest.java @@ -0,0 +1,121 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +package complex.sfx2.undo; + +import com.sun.star.text.XTextRange; +import com.sun.star.beans.XPropertySet; +import com.sun.star.table.XCell; +import com.sun.star.table.XCellRange; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextTable; +import com.sun.star.text.XText; +import com.sun.star.text.XTextDocument; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import org.openoffice.test.tools.DocumentType; +import static org.junit.Assert.*; + +/** + * implements the {@link DocumentTest} interface on top of a spreadsheet document + */ +public class WriterDocumentTest extends DocumentTestBase +{ + public WriterDocumentTest( final XMultiServiceFactory i_orb ) throws com.sun.star.uno.Exception + { + super( i_orb, DocumentType.WRITER ); + } + + public String getDocumentDescription() + { + return "text document"; + } + + public void initializeDocument() throws com.sun.star.uno.Exception + { + // TODO? + } + + public void doSingleModification() throws com.sun.star.uno.Exception + { + final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() ); + final XText docText = textDoc.getText(); + docText.setString( s_blindText ); + } + + public void verifyInitialDocumentState() throws com.sun.star.uno.Exception + { + final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() ); + final XText docText = textDoc.getText(); + assertEquals( "document should be empty", "", docText.getString() ); + } + + public void verifySingleModificationDocumentState() throws com.sun.star.uno.Exception + { + final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() ); + final XText docText = textDoc.getText(); + assertEquals( "blind text not found", s_blindText, docText.getString() ); + } + + public int doMultipleModifications() throws com.sun.star.uno.Exception + { + final XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, getDocument().getDocument() ); + final XText docText = textDoc.getText(); + + int expectedUndoActions = 0; + + // create a cursor + final XTextCursor cursor = docText.createTextCursor(); + + // create a table + final XTextTable textTable = UnoRuntime.queryInterface( XTextTable.class, + getDocument().createInstance( "com.sun.star.text.TextTable" ) ); + textTable.initialize( 3, 3 ); + final XPropertySet tableProps = UnoRuntime.queryInterface( XPropertySet.class, textTable ); + tableProps.setPropertyValue( "BackColor", 0xCCFF44 ); + + // insert the table into the doc + docText.insertTextContent( cursor, textTable, false ); + ++expectedUndoActions; //FIXME this will create 2 actions! currently the event is sent for every individual action; should it be sent for top-level actions only? how many internal actions are created is an implementation detail! + ++expectedUndoActions; + + // write some content into the center cell + final XCellRange cellRange = UnoRuntime.queryInterface( XCellRange.class, textTable ); + final XCell centerCell = cellRange.getCellByPosition( 1, 1 ); + final XTextRange cellText = UnoRuntime.queryInterface( XTextRange.class, centerCell ); + cellText.setString( "Undo Manager API Test" ); + ++expectedUndoActions; + + // give it another color + final XPropertySet cellProps = UnoRuntime.queryInterface( XPropertySet.class, centerCell ); + cellProps.setPropertyValue( "BackColor", 0x44CCFF ); + ++expectedUndoActions; + + return expectedUndoActions; + } + + private static final String s_blindText = + "Lorem ipsum dolor. Sit amet penatibus. A cum turpis. Aenean ac eu. " + + "Ligula est urna nulla vestibulum ullamcorper. Nec sit in amet tincidunt mus. " + + "Tellus sagittis mi. Suscipit cursus in vestibulum in eros ipsum felis cursus lectus " + + "nunc quis condimentum in risus nec wisi aenean luctus hendrerit magna habitasse commodo orci. " + + "Nisl etiam quis. Vestibulum justo eleifend aliquet luctus sed turpis volutpat ullamcorper " + + "aliquam penatibus sagittis pede tincidunt egestas. Nibh massa lectus. Sem mattis purus morbi " + + "scelerisque turpis donec urna phasellus. Quis at lacus. Viverra mauris mollis. " + + "Dolor tincidunt condimentum."; +} |