mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	notifications: Add a setting for changing the notification sound.
Also, add a new notification sound, "ding". It comes from https://freesound.org, where the original Zulip notification sound comes from as well. In the future, new sounds can be added by adding audio files to the `static/audio/notification_sounds` directory. Tweaked significantly by tabbott: * Avoided removing static/audio/zulip.ogg, because that file is checked for by old versions of the desktop app. * Added a views check for the sound being valid + tests. * Added additional tests. * Restructured the test_events test to be cleaner. * Removed check_bool_or_string. * Increased max length of notification_sound. * Provide available_notification_sounds in events data set if global notifications settings are requested. Fixes #8051.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							7026a8c574
						
					
				
				
					commit
					ba46dc83c6
				
			@@ -77,7 +77,12 @@ Copyright: 2003-2006 Thom May
 | 
			
		||||
           2018 Kandra Labs, Inc., and contributors
 | 
			
		||||
License: BSD-3-Clause
 | 
			
		||||
 | 
			
		||||
Files: static/audio/zulip.*
 | 
			
		||||
Files: static/audio/notification_sounds/ding.*
 | 
			
		||||
Copyright: 2017 InspectorJ
 | 
			
		||||
License: CC-BY-3.0
 | 
			
		||||
Comment: From https://freesound.org/s/411089/.
 | 
			
		||||
 | 
			
		||||
Files: static/audio/notification_sounds/zulip.*
 | 
			
		||||
Copyright: 2011 Vidsyn
 | 
			
		||||
License: CC-0-1.0
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -338,6 +338,28 @@ casper.waitUntilVisible('#language-settings-status a', function () {
 | 
			
		||||
    casper.test.assertSelectorHasText('#language-settings-status', 'Gespeichert. Bitte lade die Seite neu um die Änderungen zu aktivieren.');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
casper.then(function () {
 | 
			
		||||
    casper.waitUntilVisible('[data-section="notifications"]', function () {
 | 
			
		||||
        casper.test.info('Testing disabled/enabled behavior for Notification sound');
 | 
			
		||||
        casper.click('[data-section="notifications"]');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
casper.then(function () {
 | 
			
		||||
    // At the beginning, `#enable_sounds` will be on and `#enable_stream_sounds`
 | 
			
		||||
    // will be off by default.
 | 
			
		||||
    casper.test.assertVisible("#notification_sound:enabled", "Notification sound selector is enabled");
 | 
			
		||||
 | 
			
		||||
    casper.click('#enable_stream_sounds');
 | 
			
		||||
    casper.test.assertVisible("#notification_sound:enabled", "Notification sound selector is enabled");
 | 
			
		||||
 | 
			
		||||
    casper.click('#enable_sounds');
 | 
			
		||||
    casper.test.assertVisible("#notification_sound:enabled", "Notification sound selector is enabled");
 | 
			
		||||
 | 
			
		||||
    casper.click('#enable_stream_sounds');
 | 
			
		||||
    casper.test.assertVisible("#notification_sound:disabled", "Notification sound selector is disabled");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
casper.thenOpen("http://zulip.zulipdev.com:9981/");
 | 
			
		||||
 | 
			
		||||
// TODO: test the "Declare Zulip Bankruptcy option"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								static/audio/notification_sounds/ding.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/audio/notification_sounds/ding.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/audio/notification_sounds/ding.ogg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/audio/notification_sounds/ding.ogg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/audio/notification_sounds/zulip.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/audio/notification_sounds/zulip.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/audio/notification_sounds/zulip.ogg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/audio/notification_sounds/zulip.ogg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -64,6 +64,14 @@ exports.get_notifications = function () {
 | 
			
		||||
    return notice_memory;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function get_audio_file_path(audio_element, audio_file_without_extension) {
 | 
			
		||||
    if (audio_element.canPlayType('audio/ogg; codecs="vorbis"')) {
 | 
			
		||||
        return audio_file_without_extension + ".ogg";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return audio_file_without_extension + ".mp3";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.initialize = function () {
 | 
			
		||||
    $(window).focus(function () {
 | 
			
		||||
        window_has_focus = true;
 | 
			
		||||
@@ -86,21 +94,37 @@ exports.initialize = function () {
 | 
			
		||||
        supports_sound = false;
 | 
			
		||||
    } else {
 | 
			
		||||
        supports_sound = true;
 | 
			
		||||
 | 
			
		||||
        $("#notifications-area").append(audio);
 | 
			
		||||
        audio.append($("<source>").attr("loop", "yes"));
 | 
			
		||||
        var source = $("#notifications-area audio source");
 | 
			
		||||
 | 
			
		||||
        if (audio[0].canPlayType('audio/ogg; codecs="vorbis"')) {
 | 
			
		||||
            audio.append(
 | 
			
		||||
                $("<source>").attr("type", "audio/ogg")
 | 
			
		||||
                    .attr("loop", "yes")
 | 
			
		||||
                    .attr("src", "/static/audio/zulip.ogg"));
 | 
			
		||||
            source.attr("type", "audio/ogg");
 | 
			
		||||
        } else {
 | 
			
		||||
            audio.append(
 | 
			
		||||
                $("<source>").attr("type", "audio/mpeg")
 | 
			
		||||
                    .attr("loop", "yes")
 | 
			
		||||
                    .attr("src", "/static/audio/zulip.mp3"));
 | 
			
		||||
            source.attr("type", "audio/mpeg");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var audio_file_without_extension
 | 
			
		||||
            = "/static/audio/notification_sounds/" + page_params.notification_sound;
 | 
			
		||||
        source.attr("src", get_audio_file_path(audio[0], audio_file_without_extension));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function update_notification_sound_source() {
 | 
			
		||||
    // Simplified version of the source creation in `exports.initialize`, for
 | 
			
		||||
    // updating the source instead of creating it for the first time.
 | 
			
		||||
    var audio = $("#notifications-area audio");
 | 
			
		||||
    var source = $("#notifications-area audio source");
 | 
			
		||||
    var audio_file_without_extension
 | 
			
		||||
        = "/static/audio/notification_sounds/" + page_params.notification_sound;
 | 
			
		||||
    source.attr("src", get_audio_file_path(audio[0], audio_file_without_extension));
 | 
			
		||||
 | 
			
		||||
    // Load it so that it is ready to be played; without this the old sound
 | 
			
		||||
    // is played.
 | 
			
		||||
    $("#notifications-area").find("audio")[0].load();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.permission_state = function () {
 | 
			
		||||
    if (window.Notification === undefined) {
 | 
			
		||||
        // act like notifications are blocked if they do not have access to
 | 
			
		||||
@@ -633,6 +657,11 @@ exports.handle_global_notification_updates = function (notification_name, settin
 | 
			
		||||
    if (settings_notifications.notification_settings.indexOf(notification_name) !== -1) {
 | 
			
		||||
        page_params[notification_name] = setting;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (notification_name === "notification_sound") {
 | 
			
		||||
        // Change the sound source with the new page `notification_sound`.
 | 
			
		||||
        update_notification_sound_source();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
return exports;
 | 
			
		||||
 
 | 
			
		||||
@@ -126,6 +126,7 @@ exports.build_page = function () {
 | 
			
		||||
    var rendered_settings_tab = templates.render('settings_tab', {
 | 
			
		||||
        full_name: people.my_full_name(),
 | 
			
		||||
        page_params: page_params,
 | 
			
		||||
        enable_sound_select: page_params.enable_sounds || page_params.enable_stream_sounds,
 | 
			
		||||
        zuliprc: 'zuliprc',
 | 
			
		||||
        botserverrc: 'botserverrc',
 | 
			
		||||
        timezones: moment.tz.names(),
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ var pm_mention_notification_settings = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
var other_notification_settings = [
 | 
			
		||||
    "notification_sound",
 | 
			
		||||
    "enable_digest_emails",
 | 
			
		||||
    "enable_login_emails",
 | 
			
		||||
    "realm_name_in_notifications",
 | 
			
		||||
@@ -66,7 +67,16 @@ exports.set_up = function () {
 | 
			
		||||
 | 
			
		||||
    _.each(other_notification_settings, function (setting) {
 | 
			
		||||
        $("#" + setting).change(function () {
 | 
			
		||||
            change_notification_setting(setting, $(this).prop('checked'),
 | 
			
		||||
            var value;
 | 
			
		||||
 | 
			
		||||
            if (setting === "notification_sound") {
 | 
			
		||||
                // `notification_sound` is not a boolean.
 | 
			
		||||
                value = $(this).val();
 | 
			
		||||
            } else {
 | 
			
		||||
                value = $(this).prop('checked');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            change_notification_setting(setting, value,
 | 
			
		||||
                                        "#other-notify-settings-status");
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
@@ -83,6 +93,23 @@ exports.set_up = function () {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#play_notification_sound").click(function () {
 | 
			
		||||
        $("#notifications-area").find("audio")[0].play();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var notification_sound_dropdown = $("#notification_sound");
 | 
			
		||||
    notification_sound_dropdown.val(page_params.notification_sound);
 | 
			
		||||
 | 
			
		||||
    $("#enable_sounds, #enable_stream_sounds").change(function () {
 | 
			
		||||
        if ($("#enable_stream_sounds").prop("checked") || $("#enable_sounds").prop("checked")) {
 | 
			
		||||
            notification_sound_dropdown.prop("disabled", false);
 | 
			
		||||
            notification_sound_dropdown.parent().removeClass("control-label-disabled");
 | 
			
		||||
        } else {
 | 
			
		||||
            notification_sound_dropdown.prop("disabled", true);
 | 
			
		||||
            notification_sound_dropdown.parent().addClass("control-label-disabled");
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    $("#enable_desktop_notifications").change(function () {
 | 
			
		||||
        settings_ui.disable_sub_setting_onchange(this.checked, "pm_content_in_desktop_notifications", true);
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -129,6 +129,17 @@ label {
 | 
			
		||||
    padding: 0px 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#notification_sound,
 | 
			
		||||
#play_notification_sound {
 | 
			
		||||
    display: inline;
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
    margin-bottom: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.attributions_title {
 | 
			
		||||
    margin-top: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table.table-condensed.table-striped {
 | 
			
		||||
    margin: 0px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,23 @@
 | 
			
		||||
              "is_checked" page_params.realm_name_in_notifications
 | 
			
		||||
              "label" settings_label.realm_name_in_notifications}}
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
        <label for="notification_sound">
 | 
			
		||||
            {{t "Notification sound:" }}
 | 
			
		||||
        </label>
 | 
			
		||||
 | 
			
		||||
        <div class="input-group {{#unless enable_sound_select}}control-label-disabled{{/unless}}">
 | 
			
		||||
            <select name="notification_sound" id="notification_sound"
 | 
			
		||||
              {{#unless enable_sound_select}}
 | 
			
		||||
              disabled
 | 
			
		||||
              {{/unless}}>
 | 
			
		||||
                {{#each page_params.available_notification_sounds}}
 | 
			
		||||
                <option value="{{ this }}">{{ this }}</option>
 | 
			
		||||
                {{/each}}
 | 
			
		||||
            </select>
 | 
			
		||||
            <a id="play_notification_sound">
 | 
			
		||||
                {{t "Play sound" }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2715,6 +2715,17 @@ def bulk_add_subscriptions(streams: Iterable[Stream],
 | 
			
		||||
            [(sub.user_profile, stream) for (sub, stream) in subs_to_activate],
 | 
			
		||||
            already_subscribed)
 | 
			
		||||
 | 
			
		||||
def get_available_notification_sounds() -> List[str]:
 | 
			
		||||
    notification_sounds_path = os.path.join(settings.STATIC_ROOT, 'audio/notification_sounds')
 | 
			
		||||
    available_notification_sounds = []
 | 
			
		||||
 | 
			
		||||
    for file_name in os.listdir(notification_sounds_path):
 | 
			
		||||
        root, ext = os.path.splitext(file_name)
 | 
			
		||||
        if ext == '.ogg':
 | 
			
		||||
            available_notification_sounds.append(root)
 | 
			
		||||
 | 
			
		||||
    return available_notification_sounds
 | 
			
		||||
 | 
			
		||||
def notify_subscriptions_removed(user_profile: UserProfile, streams: Iterable[Stream],
 | 
			
		||||
                                 no_log: bool=False) -> None:
 | 
			
		||||
    if not no_log:
 | 
			
		||||
@@ -3311,7 +3322,7 @@ def do_create_realm(string_id: str, name: str,
 | 
			
		||||
                              "signups", realm.display_subdomain, signup_message)
 | 
			
		||||
    return realm
 | 
			
		||||
 | 
			
		||||
def do_change_notification_settings(user_profile: UserProfile, name: str, value: bool,
 | 
			
		||||
def do_change_notification_settings(user_profile: UserProfile, name: str, value: Union[bool, str],
 | 
			
		||||
                                    log: bool=True) -> None:
 | 
			
		||||
    """Takes in a UserProfile object, the name of a global notification
 | 
			
		||||
    preference to update, and the value to update to
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ from zerver.lib.actions import (
 | 
			
		||||
    get_status_dict, streams_to_dicts_sorted,
 | 
			
		||||
    default_stream_groups_to_dicts_sorted,
 | 
			
		||||
    get_owned_bot_dicts,
 | 
			
		||||
    get_available_notification_sounds,
 | 
			
		||||
)
 | 
			
		||||
from zerver.lib.user_groups import user_groups_in_realm_serialized
 | 
			
		||||
from zerver.tornado.event_queue import request_event_queue, get_user_events
 | 
			
		||||
@@ -285,6 +286,7 @@ def fetch_initial_state_data(user_profile: UserProfile,
 | 
			
		||||
    if want('update_global_notifications'):
 | 
			
		||||
        for notification in UserProfile.notification_setting_types:
 | 
			
		||||
            state[notification] = getattr(user_profile, notification)
 | 
			
		||||
        state['available_notification_sounds'] = get_available_notification_sounds()
 | 
			
		||||
 | 
			
		||||
    if want('zulip_version'):
 | 
			
		||||
        state['zulip_version'] = ZULIP_VERSION
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								zerver/migrations/0194_userprofile_notification_sound.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								zerver/migrations/0194_userprofile_notification_sound.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Generated by Django 1.11.6 on 2018-03-12 03:18
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('zerver', '0193_realm_email_address_visibility'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='userprofile',
 | 
			
		||||
            name='notification_sound',
 | 
			
		||||
            field=models.CharField(default='zulip', max_length=20),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -744,6 +744,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
 | 
			
		||||
    enable_stream_email_notifications = models.BooleanField(default=False)  # type: bool
 | 
			
		||||
    enable_stream_push_notifications = models.BooleanField(default=False)  # type: bool
 | 
			
		||||
    enable_stream_sounds = models.BooleanField(default=False)  # type: bool
 | 
			
		||||
    notification_sound = models.CharField(max_length=20, default='zulip')  # type: str
 | 
			
		||||
 | 
			
		||||
    # PM + @-mention notifications.
 | 
			
		||||
    enable_desktop_notifications = models.BooleanField(default=True)  # type: bool
 | 
			
		||||
@@ -865,6 +866,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
 | 
			
		||||
        enable_stream_push_notifications=bool,
 | 
			
		||||
        enable_stream_sounds=bool,
 | 
			
		||||
        message_content_in_email_notifications=bool,
 | 
			
		||||
        notification_sound=str,
 | 
			
		||||
        pm_content_in_desktop_notifications=bool,
 | 
			
		||||
        realm_name_in_notifications=bool,
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -1672,6 +1672,10 @@ class EventsRegisterTest(ZulipTestCase):
 | 
			
		||||
    @slow("Actually runs several full-stack fetching tests")
 | 
			
		||||
    def test_change_notification_settings(self) -> None:
 | 
			
		||||
        for notification_setting, v in self.user_profile.notification_setting_types.items():
 | 
			
		||||
            if notification_setting == "notification_sound":
 | 
			
		||||
                # notification_sound is tested in its own test
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            schema_checker = self.check_events_dict([
 | 
			
		||||
                ('type', equals('update_global_notifications')),
 | 
			
		||||
                ('notification_name', equals(notification_setting)),
 | 
			
		||||
@@ -1679,12 +1683,27 @@ class EventsRegisterTest(ZulipTestCase):
 | 
			
		||||
                ('setting', check_bool),
 | 
			
		||||
            ])
 | 
			
		||||
            do_change_notification_settings(self.user_profile, notification_setting, False)
 | 
			
		||||
 | 
			
		||||
            for setting_value in [True, False]:
 | 
			
		||||
                events = self.do_test(lambda: do_change_notification_settings(
 | 
			
		||||
                    self.user_profile, notification_setting, setting_value, log=False))
 | 
			
		||||
                error = schema_checker('events[0]', events[0])
 | 
			
		||||
                self.assert_on_error(error)
 | 
			
		||||
 | 
			
		||||
    def test_change_notification_sound(self) -> None:
 | 
			
		||||
        notification_setting = "notification_sound"
 | 
			
		||||
        schema_checker = self.check_events_dict([
 | 
			
		||||
            ('type', equals('update_global_notifications')),
 | 
			
		||||
            ('notification_name', equals(notification_setting)),
 | 
			
		||||
            ('user', check_string),
 | 
			
		||||
            ('setting', equals("ding")),
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
        events = self.do_test(lambda: do_change_notification_settings(
 | 
			
		||||
            self.user_profile, notification_setting, 'ding', log=False))
 | 
			
		||||
        error = schema_checker('events[0]', events[0])
 | 
			
		||||
        self.assert_on_error(error)
 | 
			
		||||
 | 
			
		||||
    def test_realm_emoji_events(self) -> None:
 | 
			
		||||
        schema_checker = self.check_events_dict([
 | 
			
		||||
            ('type', equals('realm_emoji')),
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ class HomeTest(ZulipTestCase):
 | 
			
		||||
        # Keep this list sorted!!!
 | 
			
		||||
        expected_keys = [
 | 
			
		||||
            "alert_words",
 | 
			
		||||
            "available_notification_sounds",
 | 
			
		||||
            "avatar_source",
 | 
			
		||||
            "avatar_url",
 | 
			
		||||
            "avatar_url_medium",
 | 
			
		||||
@@ -104,6 +105,7 @@ class HomeTest(ZulipTestCase):
 | 
			
		||||
            "needs_tutorial",
 | 
			
		||||
            "never_subscribed",
 | 
			
		||||
            "night_mode",
 | 
			
		||||
            "notification_sound",
 | 
			
		||||
            "password_min_guesses",
 | 
			
		||||
            "password_min_length",
 | 
			
		||||
            "pm_content_in_desktop_notifications",
 | 
			
		||||
 
 | 
			
		||||
@@ -131,9 +131,40 @@ class ChangeSettingsTest(ZulipTestCase):
 | 
			
		||||
    # This is basically a don't-explode test.
 | 
			
		||||
    def test_notify_settings(self) -> None:
 | 
			
		||||
        for notification_setting in UserProfile.notification_setting_types:
 | 
			
		||||
            # `notification_sound` is a string not a boolean, so this test
 | 
			
		||||
            # doesn't work for it.
 | 
			
		||||
            #
 | 
			
		||||
            # TODO: Make this work more like do_test_realm_update_api
 | 
			
		||||
            if notification_setting is not 'notification_sound':
 | 
			
		||||
                self.check_for_toggle_param_patch("/json/settings/notifications",
 | 
			
		||||
                                                  notification_setting)
 | 
			
		||||
 | 
			
		||||
    def test_change_notification_sound(self) -> None:
 | 
			
		||||
        pattern = "/json/settings/notifications"
 | 
			
		||||
        param = "notification_sound"
 | 
			
		||||
        user_profile = self.example_user('hamlet')
 | 
			
		||||
        self.login(user_profile.email)
 | 
			
		||||
 | 
			
		||||
        json_result = self.client_patch(pattern,
 | 
			
		||||
                                        {param: ujson.dumps("invalid")})
 | 
			
		||||
        self.assert_json_error(json_result, "Invalid notification sound 'invalid'")
 | 
			
		||||
 | 
			
		||||
        json_result = self.client_patch(pattern,
 | 
			
		||||
                                        {param: ujson.dumps("ding")})
 | 
			
		||||
        self.assert_json_success(json_result)
 | 
			
		||||
 | 
			
		||||
        # refetch user_profile object to correctly handle caching
 | 
			
		||||
        user_profile = self.example_user('hamlet')
 | 
			
		||||
        self.assertEqual(getattr(user_profile, param), "ding")
 | 
			
		||||
 | 
			
		||||
        json_result = self.client_patch(pattern,
 | 
			
		||||
                                        {param: ujson.dumps('zulip')})
 | 
			
		||||
 | 
			
		||||
        self.assert_json_success(json_result)
 | 
			
		||||
        # refetch user_profile object to correctly handle caching
 | 
			
		||||
        user_profile = self.example_user('hamlet')
 | 
			
		||||
        self.assertEqual(getattr(user_profile, param), 'zulip')
 | 
			
		||||
 | 
			
		||||
    def test_toggling_boolean_user_display_settings(self) -> None:
 | 
			
		||||
        """Test updating each boolean setting in UserProfile property_types"""
 | 
			
		||||
        boolean_settings = (s for s in UserProfile.property_types if UserProfile.property_types[s] is bool)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,8 @@ from zerver.decorator import has_request_variables, \
 | 
			
		||||
from zerver.lib.actions import do_change_password, do_change_notification_settings, \
 | 
			
		||||
    do_change_enter_sends, do_regenerate_api_key, do_change_avatar_fields, \
 | 
			
		||||
    do_set_user_display_setting, validate_email, do_change_user_delivery_email, \
 | 
			
		||||
    do_start_email_change_process, check_change_full_name, do_change_user_delivery_email
 | 
			
		||||
    do_start_email_change_process, check_change_full_name, do_change_user_delivery_email, \
 | 
			
		||||
    get_available_notification_sounds
 | 
			
		||||
from zerver.lib.avatar import avatar_url
 | 
			
		||||
from zerver.lib.send_email import send_email, FromAddress
 | 
			
		||||
from zerver.lib.i18n import get_available_language_codes
 | 
			
		||||
@@ -155,6 +156,7 @@ def json_change_notify_settings(
 | 
			
		||||
        enable_stream_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
 | 
			
		||||
        enable_stream_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
 | 
			
		||||
        enable_stream_sounds: Optional[bool]=REQ(validator=check_bool, default=None),
 | 
			
		||||
        notification_sound: Optional[str]=REQ(validator=check_string, default=None),
 | 
			
		||||
        enable_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
 | 
			
		||||
        enable_sounds: Optional[bool]=REQ(validator=check_bool, default=None),
 | 
			
		||||
        enable_offline_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
 | 
			
		||||
@@ -170,6 +172,10 @@ def json_change_notify_settings(
 | 
			
		||||
 | 
			
		||||
    # Stream notification settings.
 | 
			
		||||
 | 
			
		||||
    if (notification_sound is not None and
 | 
			
		||||
            notification_sound not in get_available_notification_sounds()):
 | 
			
		||||
        raise JsonableError(_("Invalid notification sound '%s'") % (notification_sound,))
 | 
			
		||||
 | 
			
		||||
    req_vars = {k: v for k, v in list(locals().items()) if k in user_profile.notification_setting_types}
 | 
			
		||||
 | 
			
		||||
    for k, v in list(req_vars.items()):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user