Skip to content

Commit

Permalink
Migrated TwoNote app to use the new dualScreen SDK and added UI tests…
Browse files Browse the repository at this point in the history
… for it (#46)

- Updated the dual screen SDK dependency to use the new artifacts and versions
- Refactored code to keep the same functionality
- Checked before replacing fragment whether it is already on screen through the unique tags
- Added UI tests for fragment navigation using the dual screen SDK
  • Loading branch information
bimiron authored Dec 9, 2020
1 parent 8081a44 commit 7f6602d
Show file tree
Hide file tree
Showing 15 changed files with 255 additions and 112 deletions.
6 changes: 4 additions & 2 deletions TwoNote/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ dependencies {

implementation googleDependencies.material

implementation microsoftDependencies.dualScreenLayouts
implementation microsoftDependencies.core
implementation microsoftDependencies.fluent
implementation microsoftDependencies.fragmentsHandler
implementation microsoftDependencies.screenManager
implementation microsoftDependencies.layouts

testImplementation testDependencies.junit
androidTestImplementation instrumentationTestDependencies.junit
androidTestImplementation instrumentationTestDependencies.espressoCore
androidTestImplementation instrumentationTestDependencies.testRunner
androidTestImplementation instrumentationTestDependencies.testRules
androidTestImplementation instrumentationTestDependencies.uiAutomator
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*
*/

package com.microsoft.device.display.samples.twonote

import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiDevice
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.hasToString
import org.junit.After
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(AndroidJUnit4ClassRunner::class)
class FragmentNavigationTest {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

@get:Rule
val activityRule = ActivityTestRule<MainActivity>(MainActivity::class.java)

@After
fun resetOrientation() {
device.setOrientationNatural()
device.unfreezeRotation()
}

@Test
fun test1_createNoteInSingleMode() {
onView(withId(R.id.add_fab)).check(matches(isDisplayed()))
onView(withId(R.id.add_fab)).perform(click())

onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))

spanApplication()

onView(withId(R.id.note_list_layout)).check(matches(isDisplayed()))
onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))

rotateDevice()
onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))
}

@Test
fun test2_createNoteInDualMode() {
spanApplication()

onView(withId(R.id.note_list_layout)).check(matches(isDisplayed()))

onView(withId(R.id.add_fab)).check(matches(isDisplayed()))
onView(withId(R.id.add_fab)).perform(click())

onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))

rotateDevice()
onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))
}

@Test
fun test3_openNoteFromList() {
spanApplication()
onView(withId(R.id.note_list_layout)).check(matches(isDisplayed()))

onData(hasToString("Note 1"))
.inAdapterView(withId(R.id.list_view))
.perform(click())

onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))
onView(allOf(withId(R.id.title_input), withText("Note 1"))).check(matches(isDisplayed()))

rotateDevice()
onView(withId(R.id.note_detail_layout)).check(matches(isDisplayed()))
}

@Test
fun test4_addCategoryWithoutNotes() {
onView(withId(R.id.note_list_layout)).check(matches(isDisplayed()))

spanApplication()

openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().targetContext)
onView(withText(R.string.action_add_category)).check(matches(isDisplayed()))
onView(withText(R.string.action_add_category)).perform(click())

onView(withId(R.id.note_list_layout)).check(matches(isDisplayed()))
onView(withId(R.id.get_started_layout)).check(matches(isDisplayed()))

rotateDevice()
onView(withId(R.id.note_list_layout)).check(matches(isDisplayed()))
}

private fun spanApplication() {
device.swipe(675, 1780, 1350, 900, 400)
}

private fun rotateDevice() {
device.setOrientationLeft()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

package com.microsoft.device.display.samples.twonote

import Defines.DETAIL_FRAGMENT
import Defines.GET_STARTED_FRAGMENT
import Defines.INODE
import Defines.LIST_FRAGMENT
import Defines.NOTE
Expand All @@ -23,13 +23,20 @@ import com.microsoft.device.display.samples.twonote.models.INode
import com.microsoft.device.display.samples.twonote.models.Note
import com.microsoft.device.display.samples.twonote.utils.DataProvider
import com.microsoft.device.display.samples.twonote.utils.FileSystem
import com.microsoft.device.dualscreen.core.ScreenHelper
import com.microsoft.device.dualscreen.core.ScreenMode
import com.microsoft.device.display.samples.twonote.utils.buildDetailTag
import com.microsoft.device.dualscreen.ScreenInfo
import com.microsoft.device.dualscreen.ScreenInfoListener
import com.microsoft.device.dualscreen.ScreenInfoProvider
import com.microsoft.device.dualscreen.ScreenManagerProvider

/**
* Activity that manages fragments and preservation of data through the app's lifecycle
*/
class MainActivity : AppCompatActivity(), NoteDetailFragment.OnFragmentInteractionListener {
class MainActivity :
AppCompatActivity(),
NoteDetailFragment.OnFragmentInteractionListener,
ScreenInfoListener {

companion object {
/**
* Returns whether device is rotated (to the left or right) or not
Expand All @@ -38,28 +45,43 @@ class MainActivity : AppCompatActivity(), NoteDetailFragment.OnFragmentInteracti
* @return true if rotated, false otherwise
*/
fun isRotated(context: Context): Boolean {
return ScreenHelper.getCurrentRotation(context) == Surface.ROTATION_90 ||
ScreenHelper.getCurrentRotation(context) == Surface.ROTATION_270
return ScreenInfoProvider.getScreenInfo(context).getScreenRotation() == Surface.ROTATION_90 ||
ScreenInfoProvider.getScreenInfo(context).getScreenRotation() == Surface.ROTATION_270
}
}

private var savedNote: Note? = null
private var savedINode: INode? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Get data from previously selected note (if available)
val note = savedInstanceState?.getSerializable(NOTE) as? Note
val inode = savedInstanceState?.getSerializable(INODE) as? INode
val noteSelected = note != null && inode != null
savedNote = savedInstanceState?.getSerializable(NOTE) as? Note
savedINode = savedInstanceState?.getSerializable(INODE) as? INode
}

when ((application as TwoNote).surfaceDuoScreenManager.screenMode) {
ScreenMode.SINGLE_SCREEN -> {
selectSingleScreenFragment(noteSelected, note, inode)
}
ScreenMode.DUAL_SCREEN -> {
selectDualScreenFragments(noteSelected, note, inode)
}
override fun onScreenInfoChanged(screenInfo: ScreenInfo) {
val noteSelected = savedNote != null && savedINode != null

if (screenInfo.isDualMode()) {
selectDualScreenFragments(noteSelected, savedNote, savedINode)
} else {
selectSingleScreenFragment(noteSelected, savedNote, savedINode)
}
savedNote = null
savedINode = null
}

override fun onStart() {
super.onStart()
ScreenManagerProvider.getScreenManager().addScreenInfoListener(this)
}

override fun onPause() {
super.onPause()
ScreenManagerProvider.getScreenManager().removeScreenInfoListener(this)
}

/**
Expand Down Expand Up @@ -89,7 +111,7 @@ class MainActivity : AppCompatActivity(), NoteDetailFragment.OnFragmentInteracti
*/
private fun selectDualScreenFragments(noteSelected: Boolean, note: Note?, inode: INode?) {
// If rotated, use extended canvas pattern, otherwise use list-detail pattern
if (isRotated(applicationContext)) {
if (isRotated(this)) {
// Remove fragment from second container if it exists
removeSecondFragment()

Expand Down Expand Up @@ -121,9 +143,11 @@ class MainActivity : AppCompatActivity(), NoteDetailFragment.OnFragmentInteracti
* Start note list view fragment in first container
*/
private fun startNoteListFragment() {
supportFragmentManager.beginTransaction()
.replace(R.id.first_container_id, NoteListFragment(), LIST_FRAGMENT)
.commit()
if (supportFragmentManager.findFragmentByTag(LIST_FRAGMENT) == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.first_container_id, NoteListFragment(), LIST_FRAGMENT)
.commit()
}
}

/**
Expand All @@ -134,18 +158,23 @@ class MainActivity : AppCompatActivity(), NoteDetailFragment.OnFragmentInteracti
* @param inode: inode associated with note to display in fragment
*/
private fun startNoteDetailFragment(container: Int, note: Note, inode: INode) {
supportFragmentManager.beginTransaction()
.replace(container, NoteDetailFragment.newInstance(inode, note), DETAIL_FRAGMENT)
.commit()
val tag = buildDetailTag(container, inode.id, note.id)
if (supportFragmentManager.findFragmentByTag(tag) == null) {
supportFragmentManager.beginTransaction()
.replace(container, NoteDetailFragment.newInstance(inode, note), tag)
.commit()
}
}

/**
* Start welcome fragment in second container
*/
private fun startGetStartedFragment() {
supportFragmentManager.beginTransaction()
.replace(R.id.second_container_id, GetStartedFragment(), null)
.commit()
if (supportFragmentManager.findFragmentByTag(GET_STARTED_FRAGMENT) == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.second_container_id, GetStartedFragment(), GET_STARTED_FRAGMENT)
.commit()
}
}

override fun onSaveInstanceState(outState: Bundle) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
package com.microsoft.device.display.samples.twonote

import android.app.Application
import com.microsoft.device.dualscreen.core.manager.SurfaceDuoScreenManager
import com.microsoft.device.dualscreen.ScreenManagerProvider
import com.microsoft.device.dualscreen.fragmentshandler.FragmentManagerStateHandler

/**
* Application definition that initializes dual-screen functions and managers
*/
class TwoNote : Application() {
lateinit var surfaceDuoScreenManager: SurfaceDuoScreenManager

override fun onCreate() {
super.onCreate()
surfaceDuoScreenManager = SurfaceDuoScreenManager.getInstance(this)
FragmentManagerStateHandler.initialize(this, surfaceDuoScreenManager)
ScreenManagerProvider.init(this)
FragmentManagerStateHandler.init(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@ import com.microsoft.device.display.samples.twonote.utils.DataProvider
import com.microsoft.device.display.samples.twonote.utils.DragHandler
import com.microsoft.device.display.samples.twonote.utils.FileSystem
import com.microsoft.device.display.samples.twonote.utils.PenDrawView
import com.microsoft.device.dualscreen.core.ScreenHelper
import com.microsoft.device.dualscreen.ScreenInfoProvider
import java.io.File
import java.io.FileOutputStream
import java.lang.ClassCastException
import java.time.LocalDateTime

/**
Expand Down Expand Up @@ -731,7 +730,9 @@ class NoteDetailFragment : Fragment() {
*/
fun closeFragment() {
activity?.let { activity ->
if (ScreenHelper.isDualMode(activity) && !MainActivity.isRotated(activity)) {
if (ScreenInfoProvider.getScreenInfo(activity).isDualMode() &&
!MainActivity.isRotated(activity)
) {
// Tell NoteListFragment that list data has changed
(parentFragmentManager.findFragmentByTag(LIST_FRAGMENT) as? NoteListFragment)
?.updateNotesList()
Expand Down
Loading

0 comments on commit 7f6602d

Please sign in to comment.