testapp.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // Package test provides utility drivers for running UI tests without rendering to a screen.
  2. package test // import "fyne.io/fyne/v2/test"
  3. import (
  4. "net/url"
  5. "sync"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/internal"
  8. "fyne.io/fyne/v2/internal/app"
  9. "fyne.io/fyne/v2/internal/cache"
  10. "fyne.io/fyne/v2/internal/painter"
  11. "fyne.io/fyne/v2/theme"
  12. )
  13. // ensure we have a dummy app loaded and ready to test
  14. func init() {
  15. NewApp()
  16. }
  17. type testApp struct {
  18. driver *testDriver
  19. settings *testSettings
  20. prefs fyne.Preferences
  21. propertyLock sync.RWMutex
  22. storage fyne.Storage
  23. lifecycle fyne.Lifecycle
  24. cloud fyne.CloudProvider
  25. // user action variables
  26. appliedTheme fyne.Theme
  27. lastNotification *fyne.Notification
  28. }
  29. func (a *testApp) CloudProvider() fyne.CloudProvider {
  30. return a.cloud
  31. }
  32. func (a *testApp) Icon() fyne.Resource {
  33. return nil
  34. }
  35. func (a *testApp) SetIcon(fyne.Resource) {
  36. // no-op
  37. }
  38. func (a *testApp) NewWindow(title string) fyne.Window {
  39. return a.driver.CreateWindow(title)
  40. }
  41. func (a *testApp) OpenURL(url *url.URL) error {
  42. // no-op
  43. return nil
  44. }
  45. func (a *testApp) Run() {
  46. // no-op
  47. }
  48. func (a *testApp) Quit() {
  49. // no-op
  50. }
  51. func (a *testApp) UniqueID() string {
  52. return "testApp" // TODO should this be randomised?
  53. }
  54. func (a *testApp) Driver() fyne.Driver {
  55. return a.driver
  56. }
  57. func (a *testApp) SendNotification(notify *fyne.Notification) {
  58. a.propertyLock.Lock()
  59. defer a.propertyLock.Unlock()
  60. a.lastNotification = notify
  61. }
  62. func (a *testApp) SetCloudProvider(p fyne.CloudProvider) {
  63. if p == nil {
  64. a.cloud = nil
  65. return
  66. }
  67. a.transitionCloud(p)
  68. }
  69. func (a *testApp) Settings() fyne.Settings {
  70. return a.settings
  71. }
  72. func (a *testApp) Preferences() fyne.Preferences {
  73. return a.prefs
  74. }
  75. func (a *testApp) Storage() fyne.Storage {
  76. return a.storage
  77. }
  78. func (a *testApp) Lifecycle() fyne.Lifecycle {
  79. return a.lifecycle
  80. }
  81. func (a *testApp) Metadata() fyne.AppMetadata {
  82. return fyne.AppMetadata{} // just dummy data
  83. }
  84. func (a *testApp) lastAppliedTheme() fyne.Theme {
  85. a.propertyLock.Lock()
  86. defer a.propertyLock.Unlock()
  87. return a.appliedTheme
  88. }
  89. func (a *testApp) transitionCloud(p fyne.CloudProvider) {
  90. if a.cloud != nil {
  91. a.cloud.Cleanup(a)
  92. }
  93. err := p.Setup(a)
  94. if err != nil {
  95. fyne.LogError("Failed to set up cloud provider "+p.ProviderName(), err)
  96. return
  97. }
  98. a.cloud = p
  99. listeners := a.prefs.ChangeListeners()
  100. if pp, ok := p.(fyne.CloudProviderPreferences); ok {
  101. a.prefs = pp.CloudPreferences(a)
  102. } else {
  103. a.prefs = internal.NewInMemoryPreferences()
  104. }
  105. if store, ok := p.(fyne.CloudProviderStorage); ok {
  106. a.storage = store.CloudStorage(a)
  107. } else {
  108. a.storage = &testStorage{}
  109. }
  110. for _, l := range listeners {
  111. a.prefs.AddChangeListener(l)
  112. l() // assume that preferences have changed because we replaced the provider
  113. }
  114. // after transition ensure settings listener is fired
  115. a.settings.apply()
  116. }
  117. // NewApp returns a new dummy app used for testing.
  118. // It loads a test driver which creates a virtual window in memory for testing.
  119. func NewApp() fyne.App {
  120. settings := &testSettings{scale: 1.0, theme: Theme()}
  121. prefs := internal.NewInMemoryPreferences()
  122. store := &testStorage{}
  123. test := &testApp{settings: settings, prefs: prefs, storage: store, driver: NewDriver().(*testDriver),
  124. lifecycle: &app.Lifecycle{}}
  125. root, _ := store.docRootURI()
  126. store.Docs = &internal.Docs{RootDocURI: root}
  127. painter.ClearFontCache()
  128. cache.ResetThemeCaches()
  129. fyne.SetCurrentApp(test)
  130. listener := make(chan fyne.Settings)
  131. test.Settings().AddChangeListener(listener)
  132. go func() {
  133. for {
  134. <-listener
  135. test.propertyLock.Lock()
  136. painter.ClearFontCache()
  137. cache.ResetThemeCaches()
  138. app.ApplySettings(test.Settings(), test)
  139. test.appliedTheme = test.Settings().Theme()
  140. test.propertyLock.Unlock()
  141. }
  142. }()
  143. return test
  144. }
  145. type testSettings struct {
  146. theme fyne.Theme
  147. scale float32
  148. changeListeners []chan fyne.Settings
  149. propertyLock sync.RWMutex
  150. }
  151. func (s *testSettings) AddChangeListener(listener chan fyne.Settings) {
  152. s.propertyLock.Lock()
  153. defer s.propertyLock.Unlock()
  154. s.changeListeners = append(s.changeListeners, listener)
  155. }
  156. func (s *testSettings) BuildType() fyne.BuildType {
  157. return fyne.BuildStandard
  158. }
  159. func (s *testSettings) PrimaryColor() string {
  160. return theme.ColorBlue
  161. }
  162. func (s *testSettings) SetTheme(theme fyne.Theme) {
  163. s.propertyLock.Lock()
  164. s.theme = theme
  165. s.propertyLock.Unlock()
  166. s.apply()
  167. }
  168. func (s *testSettings) ShowAnimations() bool {
  169. return true
  170. }
  171. func (s *testSettings) Theme() fyne.Theme {
  172. s.propertyLock.RLock()
  173. defer s.propertyLock.RUnlock()
  174. if s.theme == nil {
  175. return theme.DarkTheme()
  176. }
  177. return s.theme
  178. }
  179. func (s *testSettings) ThemeVariant() fyne.ThemeVariant {
  180. return 2 // not a preference
  181. }
  182. func (s *testSettings) Scale() float32 {
  183. s.propertyLock.RLock()
  184. defer s.propertyLock.RUnlock()
  185. return s.scale
  186. }
  187. func (s *testSettings) apply() {
  188. s.propertyLock.RLock()
  189. listeners := s.changeListeners
  190. s.propertyLock.RUnlock()
  191. for _, listener := range listeners {
  192. select {
  193. case listener <- s:
  194. default:
  195. l := listener
  196. go func() { l <- s }()
  197. }
  198. }
  199. }