|
1 | 1 | "Test editor, coverage 53%." |
2 | 2 |
|
3 | 3 | from idlelib import editor |
| 4 | +import os |
| 5 | +import tempfile |
| 6 | +import types |
4 | 7 | import unittest |
| 8 | +from pathlib import Path |
5 | 9 | from collections import namedtuple |
6 | 10 | from unittest import mock |
7 | 11 | from test.support import requires |
@@ -321,5 +325,81 @@ def test_rmenu_check_copy(self): |
321 | 325 | eq(self.window.rmenu_check_cut(), 'normal') |
322 | 326 |
|
323 | 327 |
|
| 328 | +class LastMtimeTest(unittest.TestCase): |
| 329 | + # Exercise last_mtime as an unbound method on a stub; no GUI needed. |
| 330 | + |
| 331 | + def test_existing_file_returns_mtime(self): |
| 332 | + with tempfile.TemporaryDirectory() as d: |
| 333 | + p = os.path.join(d, 'f.py') |
| 334 | + Path(p).touch() |
| 335 | + stub = types.SimpleNamespace(io=types.SimpleNamespace(filename=p)) |
| 336 | + self.assertEqual(Editor.last_mtime(stub), os.path.getmtime(p)) |
| 337 | + |
| 338 | + def test_deleted_file_returns_none(self): |
| 339 | + with tempfile.TemporaryDirectory() as d: |
| 340 | + p = os.path.join(d, 'gone.py') |
| 341 | + Path(p).touch() |
| 342 | + os.remove(p) |
| 343 | + stub = types.SimpleNamespace(io=types.SimpleNamespace(filename=p)) |
| 344 | + self.assertIsNone(Editor.last_mtime(stub)) |
| 345 | + |
| 346 | + def test_not_yet_created_filename(self): |
| 347 | + # __init__ calls last_mtime() before self.mtime is set, so last_mtime() |
| 348 | + # must not read self.mtime (the stub has no mtime attribute). |
| 349 | + stub = types.SimpleNamespace( |
| 350 | + io=types.SimpleNamespace(filename='/no/such/file.py')) |
| 351 | + self.assertIsNone(Editor.last_mtime(stub)) |
| 352 | + |
| 353 | + def test_no_filename_returns_none(self): |
| 354 | + stub = types.SimpleNamespace(io=types.SimpleNamespace(filename=None)) |
| 355 | + self.assertIsNone(Editor.last_mtime(stub)) |
| 356 | + |
| 357 | + |
| 358 | +class DeletedFileEventTest(unittest.TestCase): |
| 359 | + # Exercise the deleted-file handling as unbound methods; dialog is mocked. |
| 360 | + |
| 361 | + def make_stub(self): |
| 362 | + return types.SimpleNamespace( |
| 363 | + mtime=1.0, |
| 364 | + text=None, |
| 365 | + io=types.SimpleNamespace(filename='/gone.py', save_as=mock.Mock()), |
| 366 | + close=mock.Mock(), |
| 367 | + set_saved=mock.Mock(), |
| 368 | + deleted_file_event=mock.Mock(), |
| 369 | + askyesno=mock.Mock(), |
| 370 | + last_mtime=lambda: None) |
| 371 | + |
| 372 | + def test_focus_in_routes_deleted_to_dialog(self): |
| 373 | + stub = self.make_stub() |
| 374 | + Editor.focus_in_event(stub, 'event') |
| 375 | + stub.deleted_file_event.assert_called_once_with('event') |
| 376 | + stub.askyesno.assert_not_called() |
| 377 | + |
| 378 | + def _run_choice(self, choice): |
| 379 | + stub = self.make_stub() |
| 380 | + with mock.patch.object(editor.simpledialog, 'SimpleDialog') as SD: |
| 381 | + SD.return_value.go.return_value = choice |
| 382 | + Editor.deleted_file_event(stub, 'event') |
| 383 | + return stub |
| 384 | + |
| 385 | + def test_close_choice_closes_window(self): |
| 386 | + stub = self._run_choice(0) |
| 387 | + self.assertTrue(stub.close.called) |
| 388 | + # mtime is cleared before Close so the queued FocusIn does not reprompt. |
| 389 | + self.assertIsNone(stub.mtime) |
| 390 | + |
| 391 | + def test_save_as_choice_clears_mtime_and_saves(self): |
| 392 | + stub = self._run_choice(1) |
| 393 | + stub.io.save_as.assert_called_once_with('event') |
| 394 | + # A cancelled Save As leaves mtime None so it does not reprompt. |
| 395 | + self.assertIsNone(stub.mtime) |
| 396 | + |
| 397 | + def test_ignore_choice_clears_mtime(self): |
| 398 | + stub = self._run_choice(2) |
| 399 | + self.assertIsNone(stub.mtime) |
| 400 | + stub.io.save_as.assert_not_called() |
| 401 | + stub.set_saved.assert_not_called() |
| 402 | + |
| 403 | + |
324 | 404 | if __name__ == '__main__': |
325 | 405 | unittest.main(verbosity=2) |
0 commit comments